diff --git a/Makefile b/Makefile index b7a3b621..f4907db9 100644 --- a/Makefile +++ b/Makefile @@ -96,6 +96,7 @@ all-checks: clean format code-checks security generators common-checks-1 common- .PHONY: fix-abci-app-specs fix-abci-app-specs: autonomy analyse fsm-specs --update --app-class MarketManagerAbciApp --package packages/valory/skills/market_manager_abci + autonomy analyse fsm-specs --update --app-class DecisionMakerAbciApp --package packages/valory/skills/decision_maker_abci echo "Successfully validated abcis!" protolint_install: diff --git a/packages/valory/skills/decision_maker_abci/README.md b/packages/valory/skills/decision_maker_abci/README.md new file mode 100644 index 00000000..93307c4d --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/README.md @@ -0,0 +1,5 @@ +# DecisionMaker abci + +## Description + +This module contains the ABCI decision-making skill for an AEA. diff --git a/packages/valory/skills/decision_maker_abci/__init__.py b/packages/valory/skills/decision_maker_abci/__init__.py new file mode 100644 index 00000000..17b61f59 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 skill for the trader.""" + +from aea.configurations.base import PublicId + + +PUBLIC_ID = PublicId.from_str("valory/decision_maker_abci:0.1.0") diff --git a/packages/valory/skills/decision_maker_abci/behaviours/__init__.py b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py new file mode 100644 index 00000000..c0db5476 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 'decision_maker_abci' skill.""" diff --git a/packages/valory/skills/decision_maker_abci/behaviours/base.py b/packages/valory/skills/decision_maker_abci/behaviours/base.py new file mode 100644 index 00000000..ab16a286 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/base.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 behaviour for the 'decision_maker_abci' skill.""" + +from abc import ABC +from typing import Generator, cast + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload +from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour +from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams +from packages.valory.skills.decision_maker_abci.states.base import SynchronizedData + + +class DecisionMakerBaseBehaviour(BaseBehaviour, ABC): + """Represents the base class for the decision-making FSM behaviour.""" + + @property + def params(self) -> DecisionMakerParams: + """Return the params.""" + return cast(DecisionMakerParams, self.context.params) + + @property + def synchronized_data(self) -> SynchronizedData: + """Return the synchronized data.""" + return cast(SynchronizedData, super().synchronized_data) + + def finish_behaviour(self, payload: BaseTxPayload) -> Generator: + """Finish the behaviour.""" + with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): + yield from self.send_a2a_transaction(payload) + yield from self.wait_until_round_end() + + self.set_done() diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py new file mode 100644 index 00000000..cdc20fba --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 for the blacklisting of the sampled bet.""" + +from typing import Generator, Optional + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.states.blacklisting import ( + BlacklistingRound, +) +from packages.valory.skills.market_manager_abci.bets import BetStatus, serialize_bets +from packages.valory.skills.market_manager_abci.payloads import UpdateBetsPayload + + +class BlacklistingBehaviour(DecisionMakerBaseBehaviour): + """A behaviour in which the agents blacklist the sampled bet.""" + + matching_round = BlacklistingRound + + @property + def synced_time(self) -> float: + """Get the synchronized time among agents.""" + synced_time = self.shared_state.round_sequence.last_round_transition_timestamp + return synced_time.timestamp() + + def _blacklist(self) -> Optional[str]: + """Blacklist the sampled bet and return the updated version of the bets, serialized.""" + bets = self.synchronized_data.bets + sampled_bet_index = self.synchronized_data.sampled_bet_index + sampled_bet = bets[sampled_bet_index] + sampled_bet.status = BetStatus.BLACKLISTED + blacklist_expiration = self.synced_time + self.params.blacklisting_duration + sampled_bet.blacklist_expiration = blacklist_expiration + + return serialize_bets(bets) + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + payload = UpdateBetsPayload(self.context.agent_address, self._blacklist()) + + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py new file mode 100644 index 00000000..d3611b37 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 for the decision-making of the skill.""" + +from multiprocessing.pool import AsyncResult +from pathlib import Path +from string import Template +from typing import Any, Generator, Optional, Tuple, cast + +from mech_client.interact import PRIVATE_KEY_FILE_PATH + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.states.decision_maker import ( + DecisionMakerRound, +) +from packages.valory.skills.decision_maker_abci.tasks import ( + MechInteractionResponse, + MechInteractionTask, +) +from packages.valory.skills.market_manager_abci.bets import BINARY_N_SLOTS + + +BET_PROMPT = Template( + """ + With the given question "${question}" + and the `yes` option represented by ${yes} + and the `no` option represented by ${no}, + what are the respective probabilities of `p_yes` and `p_no` occurring? + """ +) + + +class DecisionMakerBehaviour(DecisionMakerBaseBehaviour): + """A behaviour in which the agents decide which answer they are going to choose for the next bet.""" + + matching_round = DecisionMakerRound + + def __init__(self, **kwargs: Any) -> None: + """Initialize Behaviour.""" + super().__init__(**kwargs) + self._async_result: Optional[AsyncResult] = None + + @property + def n_slots_unsupported(self) -> bool: + """Whether the behaviour supports the current number of slots as it currently only supports binary decisions.""" + return self.params.slot_count != BINARY_N_SLOTS + + def setup(self) -> None: + """Setup behaviour.""" + if self.n_slots_unsupported: + return + + mech_task = MechInteractionTask() + sampled_bet = self.synchronized_data.sampled_bet + prompt_params = dict( + question=sampled_bet.title, yes=sampled_bet.yes, no=sampled_bet.no + ) + task_kwargs = dict( + prompt=BET_PROMPT.substitute(prompt_params), + agent_id=self.params.mech_agent_id, + tool=self.params.mech_tool, + private_key_path=str(Path(self.context.data_dir) / PRIVATE_KEY_FILE_PATH), + ) + task_id = self.context.task_manager.enqueue_task(mech_task, kwargs=task_kwargs) + self._async_result = self.context.task_manager.get_task_result(task_id) + + def _get_decision( + self, + ) -> Generator[None, None, Optional[Tuple[Optional[int], Optional[float]]]]: + """Get the vote and it's confidence.""" + if self._async_result is None: + return None, None + + if not self._async_result.ready(): + self.context.logger.debug("The decision making task is not finished yet.") + yield from self.sleep(self.params.sleep_time) + return None + + # Get the decision from the task. + mech_response = cast(MechInteractionResponse, self._async_result.get()) + self.context.logger.info(f"Decision has been received:\n{mech_response}") + + if mech_response.prediction is None: + self.context.logger.info( + f"There was an error on the mech response: {mech_response.error}" + ) + return None, None + + return mech_response.prediction.vote, mech_response.prediction.confidence + + def _is_profitable(self, vote: Optional[int], confidence: Optional[float]) -> bool: + """Whether the decision is profitable or not.""" + if vote is None or confidence is None: + return False + + bet_amount = self.params.get_bet_amount(confidence) + fee = self.synchronized_data.sampled_bet.fee + bet_threshold = self.params.bet_threshold + return bet_amount - fee >= bet_threshold + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + decision = yield from self._get_decision() + if decision is None: + return + + vote, confidence = decision + is_profitable = self._is_profitable(vote, confidence) + payload = DecisionMakerPayload( + self.context.agent_address, + self.n_slots_unsupported, + is_profitable, + vote, + confidence, + ) + + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py new file mode 100644 index 00000000..7b8acd62 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the round behaviour for the 'decision_maker_abci' skill.""" + +from typing import Set, Type + +from packages.valory.skills.abstract_round_abci.behaviours import ( + AbstractRoundBehaviour, + BaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.behaviours.blacklisting import ( + BlacklistingBehaviour, +) +from packages.valory.skills.decision_maker_abci.behaviours.decision_maker import ( + DecisionMakerBehaviour, +) +from packages.valory.skills.decision_maker_abci.behaviours.sampling import ( + SamplingBehaviour, +) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp + + +class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): + """This behaviour manages the consensus stages for the decision-making.""" + + initial_behaviour_cls = DecisionMakerBehaviour + abci_app_cls = DecisionMakerAbciApp + behaviours: Set[Type[BaseBehaviour]] = { + SamplingBehaviour, # type: ignore + DecisionMakerBehaviour, # type: ignore + BlacklistingBehaviour, # type: ignore + } diff --git a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py new file mode 100644 index 00000000..0c6991ee --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 for sampling a bet.""" + +from typing import Generator, Iterator + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload +from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound +from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus + + +class SamplingBehaviour(DecisionMakerBaseBehaviour): + """A behaviour in which the agents blacklist the sampled bet.""" + + matching_round = SamplingRound + + @property + def available_bets(self) -> Iterator[Bet]: + """Get an iterator of the unprocessed bets.""" + bets = self.synchronized_data.bets + return filter(lambda bet: bet.status == BetStatus.UNPROCESSED, bets) + + @property + def sampled_bet_idx(self) -> int: + """ + Sample a bet and return its id. + + The sampling logic is relatively simple at the moment + It simply selects the unprocessed bet with the largest liquidity. + """ + max_lq = max(self.available_bets, key=lambda bet: bet.usdLiquidityMeasure) + return self.synchronized_data.bets.index(max_lq) + + def async_act(self) -> Generator: + """Do the action.""" + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + payload = SamplingPayload(self.context.agent_address, self.sampled_bet_idx) + + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/dialogues.py b/packages/valory/skills/decision_maker_abci/dialogues.py new file mode 100644 index 00000000..e985dc86 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/dialogues.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogue as BaseAbciDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogues as BaseAbciDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogue as BaseContractApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogues as BaseContractApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogue as BaseHttpDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogues as BaseHttpDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogue as BaseIpfsDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogues as BaseIpfsDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogue as BaseLedgerApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogues as BaseLedgerApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogue as BaseSigningDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogues as BaseSigningDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogue as BaseTendermintDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogues as BaseTendermintDialogues, +) + + +AbciDialogue = BaseAbciDialogue +AbciDialogues = BaseAbciDialogues + + +HttpDialogue = BaseHttpDialogue +HttpDialogues = BaseHttpDialogues + + +SigningDialogue = BaseSigningDialogue +SigningDialogues = BaseSigningDialogues + + +LedgerApiDialogue = BaseLedgerApiDialogue +LedgerApiDialogues = BaseLedgerApiDialogues + + +ContractApiDialogue = BaseContractApiDialogue +ContractApiDialogues = BaseContractApiDialogues + + +TendermintDialogue = BaseTendermintDialogue +TendermintDialogues = BaseTendermintDialogues + + +IpfsDialogue = BaseIpfsDialogue +IpfsDialogues = BaseIpfsDialogues diff --git a/packages/valory/skills/decision_maker_abci/handlers.py b/packages/valory/skills/decision_maker_abci/handlers.py new file mode 100644 index 00000000..060a2155 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/handlers.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 'decision_maker_abci' skill.""" + +from packages.valory.skills.abstract_round_abci.handlers import ( + ABCIRoundHandler as BaseABCIRoundHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + ContractApiHandler as BaseContractApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + HttpHandler as BaseHttpHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + IpfsHandler as BaseIpfsHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + LedgerApiHandler as BaseLedgerApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + SigningHandler as BaseSigningHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + TendermintHandler as BaseTendermintHandler, +) + + +ABCIHandler = BaseABCIRoundHandler +HttpHandler = BaseHttpHandler +SigningHandler = BaseSigningHandler +LedgerApiHandler = BaseLedgerApiHandler +ContractApiHandler = BaseContractApiHandler +TendermintHandler = BaseTendermintHandler +IpfsHandler = BaseIpfsHandler diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py new file mode 100644 index 00000000..597239b0 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the models for the skill.""" + +from typing import Any, Dict + +from packages.valory.skills.abstract_round_abci.models import ( + BenchmarkTool as BaseBenchmarkTool, +) +from packages.valory.skills.abstract_round_abci.models import Requests as BaseRequests +from packages.valory.skills.abstract_round_abci.models import ( + SharedState as BaseSharedState, +) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp +from packages.valory.skills.market_manager_abci.models import ( + MarketManagerParams as BaseParams, +) + + +Requests = BaseRequests +BenchmarkTool = BaseBenchmarkTool + + +class SharedState(BaseSharedState): + """Keep the current shared state of the skill.""" + + abci_app_cls = DecisionMakerAbciApp + + +class DecisionMakerParams(BaseParams): + """Decision maker's parameters.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Initialize the parameters' object.""" + self.mech_agent_id: int = self._ensure("mech_agent_id", kwargs, int) + self.mech_tool: int = self._ensure("mech_tool", kwargs, str) + # this is a mapping from the confidence of a bet's choice to the amount we are willing to bet + self.bet_amount_per_threshold: Dict[float, int] = self._ensure( + "bet_amount_per_threshold", kwargs, Dict[float, int] + ) + # the threshold amount in WEI starting from which we are willing to place a bet + self.bet_threshold: int = self._ensure("bet_threshold", kwargs, str) + # the duration, in seconds, of blacklisting a bet before retrying to make an estimate for it + self.blacklisting_duration: int = self._ensure( + "blacklisting_duration", kwargs, int + ) + super().__init__(*args, **kwargs) + + def get_bet_amount(self, confidence: float) -> int: + """Get the bet amount given a prediction's confidence.""" + threshold = round(confidence, 1) + return self.bet_amount_per_threshold[threshold] diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py new file mode 100644 index 00000000..0f658bfc --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the transaction payloads for the decision maker.""" + +from dataclasses import dataclass +from typing import Optional + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload + + +@dataclass(frozen=True) +class DecisionMakerPayload(BaseTxPayload): + """Represents a transaction payload for the decision-making.""" + + unsupported: bool + is_profitable: bool + vote: Optional[int] + confidence: Optional[float] + + +@dataclass(frozen=True) +class SamplingPayload(BaseTxPayload): + """Represents a transaction payload for the sampling of a bet.""" + + index: int diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py new file mode 100644 index 00000000..5429d924 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the rounds for the decision-making.""" + +from typing import Dict, Set + +from packages.valory.skills.abstract_round_abci.base import ( + AbciApp, + AbciAppTransitionFunction, + AppState, +) +from packages.valory.skills.decision_maker_abci.states.base import Event +from packages.valory.skills.decision_maker_abci.states.blacklisting import ( + BlacklistingRound, +) +from packages.valory.skills.decision_maker_abci.states.decision_maker import ( + DecisionMakerRound, +) +from packages.valory.skills.decision_maker_abci.states.final_states import ( + FinishedDecisionMakerRound, + ImpossibleRound, +) +from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound + + +class DecisionMakerAbciApp(AbciApp[Event]): + """DecisionMakerAbciApp + + Initial round: DecisionMakerRound + + Initial states: {DecisionMakerRound} + + Transition states: + + Final states: {FinishedDecisionMakerRound} + + Timeouts: + round timeout: 30.0 + """ + + initial_round_cls: AppState = SamplingRound + initial_states: Set[AppState] = {SamplingRound} + transition_function: AbciAppTransitionFunction = { + SamplingRound: { + Event.DONE: DecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: SamplingRound, + }, + DecisionMakerRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.MECH_RESPONSE_ERROR: BlacklistingRound, + Event.NO_MAJORITY: DecisionMakerRound, + Event.NON_BINARY: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.TIE: BlacklistingRound, + Event.UNPROFITABLE: BlacklistingRound, + }, + BlacklistingRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: BlacklistingRound, + }, + FinishedDecisionMakerRound: {}, + ImpossibleRound: {}, + } + final_states: Set[AppState] = { + ImpossibleRound, + FinishedDecisionMakerRound, + } + event_to_timeout: Dict[Event, float] = { + Event.ROUND_TIMEOUT: 30.0, + } + db_pre_conditions: Dict[AppState, Set[str]] = { + SamplingRound: set(), + } + db_post_conditions: Dict[AppState, Set[str]] = { + FinishedDecisionMakerRound: set(), + ImpossibleRound: set(), + } diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml new file mode 100644 index 00000000..f21d3846 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -0,0 +1,159 @@ +name: decision_maker_abci +author: valory +version: 0.1.0 +type: skill +description: ABCI application for common apps. +license: Apache-2.0 +aea_version: '>=1.0.0, <2.0.0' +fingerprint: + README.md: bafybeieztbubb6yn5umyt5ulknvb2xxppz5ecxaosxqsaejnrcrrwfu2ji + __init__.py: bafybeigqj2uodavhrygpqn6iah3ljp53z54c5fxyh5ykgkxuhh5lof6pda + behaviours.py: bafybeihtnjhozgnfasa4weq4tdoj74z5n7bkoqquh6h6eb2ykckmyamxny + dialogues.py: bafybeicm4bqedlyytfo4icqqbyolo36j2hk7pqh32d3zc5yqg75bt4demm + fsm_specification.yaml: bafybeicx5eutgr4lin7mhwr73xhanuzwdmps7pfoy5f2k7gfxmuec4qbyu + handlers.py: bafybeifby6yecei2d7jvxbqrc3tpyemb7xdb4ood2kny5dqja26qnxrf24 + models.py: bafybeifkfjsfkjy2x32cbuoewxujfgpcl3wk3fji6kq27ofr2zcfe7l5oe + payloads.py: bafybeiacrixfazch2a5ydj7jfk2pnvlxwkygqlwzkfmdeldrj4fqgwyyzm + rounds.py: bafybeicaewfjtvn4tg3te2glocojmd2gzqkjznv2ruyeev22yrw6ed3wu4 + tests/__init__.py: bafybeiab2s4vkmbz5bc4wggcclapdbp65bosv4en5zaazk5dwmldojpqja + tests/test_behaviours.py: bafybeiffgw2msg466lrtebl6cqa3736iuis2juf33a6awj7wzrcn5yxi6u + tests/test_dialogues.py: bafybeibeqnpzuzgcfb6yz76htslwsbbpenihswbp7j3qdyq42yswjq25l4 + tests/test_handlers.py: bafybeifpnwaktxckbvclklo6flkm5zqs7apmb33ffs4jrmunoykjbl5lni + tests/test_models.py: bafybeiewxl7nio5av2aukql2u7hlhodzdvjjneleba32abr42xeirrycb4 + tests/test_payloads.py: bafybeifik6ek75ughyd4y6t2lchlmjadkzbrz4hsb332k6ul4pwhlo2oga + tests/test_rounds.py: bafybeiefwk4quzkdn7m43rlahdhoe53xtzm4itlznjh26vdt5567oblgdm +fingerprint_ignore_patterns: [] +connections: [] +contracts: +- valory/gnosis_safe:0.1.0:bafybeif5fdwoxq5mscrurtuimadmtctyxxeeui45u4g6leqobzls7bsl3u +protocols: +- valory/contract_api:1.0.0:bafybeidv6wxpjyb2sdyibnmmum45et4zcla6tl63bnol6ztyoqvpl4spmy +skills: +- valory/abstract_round_abci:0.1.0:bafybeiac62ennpw54gns2quk4g3yoaili2mb72nj6c52czobz5dcwj4mwi +- valory/market_manager_abci:0.1.0:bafybeiunkownhashhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +behaviours: + main: + args: {} + class_name: AgentDecisionMakerRoundBehaviour +handlers: + abci: + args: {} + class_name: ABCIHandler + contract_api: + args: {} + class_name: ContractApiHandler + http: + args: {} + class_name: HttpHandler + ipfs: + args: {} + class_name: IpfsHandler + ledger_api: + args: {} + class_name: LedgerApiHandler + signing: + args: {} + class_name: SigningHandler + tendermint: + args: {} + class_name: TendermintHandler +models: + abci_dialogues: + args: {} + class_name: AbciDialogues + benchmark_tool: + args: + log_dir: /logs + class_name: BenchmarkTool + contract_api_dialogues: + args: {} + class_name: ContractApiDialogues + http_dialogues: + args: {} + class_name: HttpDialogues + ipfs_dialogues: + args: {} + class_name: IpfsDialogues + ledger_api_dialogues: + args: {} + class_name: LedgerApiDialogues + params: + args: + cleanup_history_depth: 1 + cleanup_history_depth_current: null + drand_public_key: 868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31 + genesis_config: + genesis_time: '2022-05-20T16:00:21.735122717Z' + chain_id: chain-c4daS1 + consensus_params: + block: + max_bytes: '22020096' + max_gas: '-1' + time_iota_ms: '1000' + evidence: + max_age_num_blocks: '100000' + max_age_duration: '172800000000000' + max_bytes: '1048576' + validator: + pub_key_types: + - ed25519 + version: {} + voting_power: '10' + keeper_timeout: 30.0 + max_attempts: 10 + max_healthcheck: 120 + on_chain_service_id: null + request_retry_delay: 1.0 + request_timeout: 10.0 + reset_pause_duration: 10 + reset_tendermint_after: 2 + retry_attempts: 400 + retry_timeout: 3 + round_timeout_seconds: 30.0 + service_id: decision_maker + service_registry_address: null + setup: + all_participants: + - '0x0000000000000000000000000000000000000000' + safe_contract_address: '0x0000000000000000000000000000000000000000' + consensus_threshold: null + share_tm_config_on_startup: false + sleep_time: 1 + tendermint_check_sleep_delay: 3 + tendermint_com_url: http://localhost:8080 + tendermint_max_retries: 5 + tendermint_p2p_url: localhost:26656 + tendermint_url: http://localhost:26657 + tx_timeout: 10.0 + use_termination: false + mech_agent_id: 3 + mech_tool: prediction-online + bet_amount_per_threshold: + 0: 0 + 0.1: 0 + 0.2: 0 + 0.3: 0 + 0.4: 0 + 0.5: 0 + 0.6: 0 + 0.7: 0 + 0.8: 0 + 0.9: 0 + 1: 0 + bet_threshold: 100000000000000000 + blacklisting_duration: 3600 + class_name: DecisionMakerParams + requests: + args: {} + class_name: Requests + signing_dialogues: + args: {} + class_name: SigningDialogues + state: + args: {} + class_name: SharedState + tendermint_dialogues: + args: {} + class_name: TendermintDialogues +dependencies: {} +is_abstract: true diff --git a/packages/valory/skills/decision_maker_abci/states/__init__.py b/packages/valory/skills/decision_maker_abci/states/__init__.py new file mode 100644 index 00000000..bddac561 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 rounds for the 'decision_maker_abci' skill.""" diff --git a/packages/valory/skills/decision_maker_abci/states/base.py b/packages/valory/skills/decision_maker_abci/states/base.py new file mode 100644 index 00000000..8b7dcf93 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/base.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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 functionality for the rounds of the decision-making abci app.""" + +from enum import Enum + +from packages.valory.skills.abstract_round_abci.base import DeserializedCollection +from packages.valory.skills.market_manager_abci.bets import Bet +from packages.valory.skills.market_manager_abci.rounds import ( + SynchronizedData as BaseSynchronizedData, +) + + +class Event(Enum): + """Event enumeration for the price estimation demo.""" + + DONE = "done" + NONE = "none" + MECH_RESPONSE_ERROR = "mech_response_error" + NON_BINARY = "non_binary" + TIE = "tie" + UNPROFITABLE = "unprofitable" + ROUND_TIMEOUT = "round_timeout" + NO_MAJORITY = "no_majority" + + +class SynchronizedData(BaseSynchronizedData): + """Class to represent the synchronized data. + + This data is replicated by the tendermint application. + """ + + @property + def sampled_bet_index(self) -> int: + """Get the sampled bet.""" + return int(self.db.get_strict("sampled_bet_index")) + + @property + def sampled_bet(self) -> Bet: + """Get the sampled bet.""" + return self.bets[self.sampled_bet_index] + + @property + def non_binary(self) -> bool: + """Get whether the question is non-binary.""" + return bool(self.db.get_strict("non_binary")) + + @property + def vote(self) -> str: + """Get the bet's vote.""" + vote = self.db.get_strict("vote") + return self.sampled_bet.get_outcome(vote) + + @property + def confidence(self) -> float: + """Get the vote's confidence.""" + return float(self.db.get_strict("confidence")) + + @property + def is_profitable(self) -> bool: + """Get whether the current vote is profitable or not.""" + return bool(self.db.get_strict("is_profitable")) + + @property + def participant_to_decision(self) -> DeserializedCollection: + """Get the participants to decision-making.""" + return self._get_deserialized("participant_to_decision") + + @property + def participant_to_sampling(self) -> DeserializedCollection: + """Get the participants to bet-sampling.""" + return self._get_deserialized("participant_to_sampling") diff --git a/packages/valory/skills/decision_maker_abci/states/blacklisting.py b/packages/valory/skills/decision_maker_abci/states/blacklisting.py new file mode 100644 index 00000000..3090849d --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/blacklisting.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the blacklisting state of the decision-making abci app.""" + +from packages.valory.skills.decision_maker_abci.states.base import Event +from packages.valory.skills.market_manager_abci.rounds import ( + UpdateBetsRound as BaseUpdateBetsRound, +) + + +class BlacklistingRound(BaseUpdateBetsRound): + """A round for updating the bets after blacklisting the sampled one.""" + + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY diff --git a/packages/valory/skills/decision_maker_abci/states/decision_maker.py b/packages/valory/skills/decision_maker_abci/states/decision_maker.py new file mode 100644 index 00000000..4d11731f --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/decision_maker.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF 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-making state of the decision-making abci app.""" + +from enum import Enum +from typing import Optional, Tuple, cast + +from packages.valory.skills.abstract_round_abci.base import ( + CollectSameUntilThresholdRound, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.states.base import ( + Event, + SynchronizedData, +) + + +class DecisionMakerRound(CollectSameUntilThresholdRound): + """A round in which the agents decide on the bet's answer.""" + + payload_class = DecisionMakerPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + none_event = Event.MECH_RESPONSE_ERROR + no_majority_event = Event.NO_MAJORITY + selection_key = ( + get_name(SynchronizedData.non_binary), + get_name(SynchronizedData.vote), + get_name(SynchronizedData.confidence), + get_name(SynchronizedData.is_profitable), + ) + collection_key = get_name(SynchronizedData.participant_to_decision) + + def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: + """Process the end of the block.""" + res = super().end_block() + if res is None: + return None + + synced_data, event = cast(Tuple[SynchronizedData, Enum], res) + if event == Event.DONE and synced_data.non_binary: + return synced_data, Event.NON_BINARY + + if event == Event.DONE and synced_data.vote is None: + return synced_data, Event.TIE + + if event == Event.DONE and not synced_data.is_profitable: + return synced_data, Event.UNPROFITABLE + + return synced_data, event diff --git a/packages/valory/skills/decision_maker_abci/states/final_states.py b/packages/valory/skills/decision_maker_abci/states/final_states.py new file mode 100644 index 00000000..eac15d5e --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/final_states.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the final states of the decision-making abci app.""" + +from packages.valory.skills.abstract_round_abci.base import DegenerateRound + + +class FinishedDecisionMakerRound(DegenerateRound): + """A round representing that decision-making has finished.""" + + +class ImpossibleRound(DegenerateRound): + """A round representing that decision-making is impossible with the given parametrization.""" diff --git a/packages/valory/skills/decision_maker_abci/states/sampling.py b/packages/valory/skills/decision_maker_abci/states/sampling.py new file mode 100644 index 00000000..bca706e3 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/sampling.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the sampling state of the decision-making abci app.""" + +from packages.valory.skills.abstract_round_abci.base import ( + CollectSameUntilThresholdRound, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload +from packages.valory.skills.decision_maker_abci.states.base import ( + Event, + SynchronizedData, +) + + +class SamplingRound(CollectSameUntilThresholdRound): + """A round for sampling a bet.""" + + payload_class = SamplingPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY + selection_key = get_name(SynchronizedData.sampled_bet_index) + collection_key = get_name(SynchronizedData.participant_to_sampling) diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py new file mode 100644 index 00000000..61428a47 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 background tasks of the decision maker skill.""" + +from dataclasses import dataclass +from typing import Any, Optional + +from aea.skills.tasks import Task +from mech_client.interact import interact + + +@dataclass +class PredictionResponse: + """A response of a prediction.""" + + p_yes: float + p_no: float + confidence: float + info_utility: float + + def __post_init__(self) -> None: + """Runs checks on whether the current prediction response is valid or not.""" + # all the fields are probabilities + probabilities = (getattr(self, field) for field in self.__annotations__) + if ( + any(not (0 <= prob <= 1) for prob in probabilities) + or self.p_yes + self.p_no != 1 + ): + raise ValueError("Invalid prediction response initialization.") + + @property + def vote(self) -> Optional[int]: + """Return the vote. `0` represents "yes" and `1` represents "no".""" + if self.p_no != self.p_yes: + return int(self.p_no > self.p_yes) + return None + + +@dataclass +class MechInteractionResponse: + """A structure for the response of a mech interaction task.""" + + prediction: Optional[PredictionResponse] = None + error: str = "Unknown" + + +class MechInteractionTask(Task): + """Perform an interaction with a mech.""" + + def execute( + self, + *args: Any, + **kwargs: Any, + ) -> MechInteractionResponse: + """Execute the task.""" + res = interact(*args, **kwargs) + + try: + prediction = PredictionResponse(**res) + except (ValueError, TypeError): + error_msg = f"The response's format was unexpected: {res}" + return MechInteractionResponse(error=error_msg) + else: + return MechInteractionResponse(prediction) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index 0fea9186..d0907c2c 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -19,12 +19,15 @@ """This module contains the behaviours for the MarketManager skill.""" -import json -from typing import Any, Dict, Generator, List, Optional, Set, Type +from typing import Any, Generator, Iterator, List, Set, Type from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.behaviours import AbstractRoundBehaviour -from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus, BetsEncoder +from packages.valory.skills.market_manager_abci.bets import ( + Bet, + BetStatus, + serialize_bets, +) from packages.valory.skills.market_manager_abci.graph_tooling.requests import ( FetchStatus, QueryingBehaviour, @@ -45,19 +48,12 @@ def __init__(self, **kwargs: Any) -> None: """Initialize `UpdateBetsBehaviour`.""" super().__init__(**kwargs) # list of bets mapped to prediction markets - self.bets: Dict[str, List[Bet]] = {} - - @property - def serialized_bets(self) -> Optional[str]: - """Get the bets serialized.""" - if len(self.bets) == 0: - return None - return json.dumps(self.bets, cls=BetsEncoder) + self.bets: List[Bet] = [] @property def bets_ids(self) -> List[str]: """Get the ids of the already existing bets.""" - return [bet.id for bets in self.bets.values() for bet in bets] + return [bet.id for bet in self.bets] def is_valid_bet(self, bet: Bet) -> bool: """Return if a bet is valid or not.""" @@ -67,18 +63,15 @@ def is_valid_bet(self, bet: Bet) -> bool: ) @property - def valid_local_bets(self) -> Dict[str, List[Bet]]: + def valid_local_bets(self) -> Iterator[Bet]: """Get the valid already existing bets.""" - return { - market: list(filter(self.is_valid_bet, bets)) - for market, bets in self.synchronized_data.bets.items() - } + return filter(self.is_valid_bet, self.synchronized_data.bets) def _update_bets( self, ) -> Generator: """Fetch the questions from all the prediction markets and update the local copy of the bets.""" - self.bets = self.valid_local_bets + self.bets = list(self.valid_local_bets) existing_ids = self.bets_ids while True: @@ -89,21 +82,21 @@ def _update_bets( bets_market_chunk = yield from self._fetch_bets() if bets_market_chunk is not None: bets_updates = ( - Bet(**bet) + Bet(**bet, market=self._current_market) for bet in bets_market_chunk - if bet.id not in existing_ids + if bet.id not in existing_ids and bet.outcomes is not None ) - self.bets[self._current_market].extend(bets_updates) + self.bets.extend(bets_updates) if self._fetch_status != FetchStatus.SUCCESS: - self.bets = {} + self.bets = [] def async_act(self) -> Generator: """Do the action.""" with self.context.benchmark_tool.measure(self.behaviour_id).local(): yield from self._update_bets() payload = UpdateBetsPayload( - self.context.agent_address, self.serialized_bets + self.context.agent_address, serialize_bets(self.bets) ) with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 7f9e23f5..0d13e90c 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -26,13 +26,14 @@ from typing import Any, Dict, List, Optional, Union +BINARY_N_SLOTS = 2 + + class BetStatus(Enum): """A bet's status.""" UNPROCESSED = auto() PROCESSED = auto() - WAITING_RESPONSE = auto() - RESPONSE_RECEIVED = auto() BLACKLISTED = auto() @@ -41,6 +42,7 @@ class Bet: """A bet's structure.""" id: str + market: str title: str creator: str fee: int @@ -49,17 +51,55 @@ class Bet: outcomeTokenAmounts: List[int] outcomeTokenMarginalPrices: List[float] outcomes: Optional[List[str]] + usdLiquidityMeasure: int status: BetStatus = BetStatus.UNPROCESSED blacklist_expiration: float = -1 def __post_init__(self) -> None: """Post initialization to adjust the values.""" - if self.outcomes == "null": + if ( + self.outcomes is None + or self.outcomes == "null" + or len(self.outcomes) + != len(self.outcomeTokenAmounts) + != len(self.outcomeTokenMarginalPrices) + != self.outcomeSlotCount + ): self.outcomes = None if isinstance(self.status, int): super().__setattr__("status", BetStatus(self.status)) + def get_outcome(self, index: int) -> str: + """Get an outcome given its index.""" + if self.outcomes is None: + raise ValueError(f"Bet {self} has an incorrect outcomes list of `None`.") + try: + return self.outcomes[index] + except KeyError as exc: + error = f"Cannot get outcome with index {index} from {self.outcomes}" + raise ValueError(error) from exc + + def _get_binary_outcome(self, no: bool) -> str: + """Get an outcome only if it is binary.""" + if self.outcomeSlotCount == BINARY_N_SLOTS: + return self.get_outcome(int(no)) + requested_outcome = "no" if no else "yes" + error = ( + f"A {requested_outcome!r} outcome is only available for binary questions." + ) + raise ValueError(error) + + @property + def yes(self) -> str: + """Return the "yes" outcome.""" + return self._get_binary_outcome(False) + + @property + def no(self) -> str: + """Return the "no" outcome.""" + return self._get_binary_outcome(True) + class BetsEncoder(json.JSONEncoder): """JSON encoder for bets.""" @@ -87,3 +127,10 @@ def hook(data: Dict[str, Any]) -> Union[Bet, Dict[str, Bet]]: return Bet(**data) return data + + +def serialize_bets(bets: List[Bet]) -> Optional[str]: + """Get the bets serialized.""" + if len(bets) == 0: + return None + return json.dumps(bets, cls=BetsEncoder) diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py index 77b300c0..b7386962 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py @@ -45,6 +45,7 @@ outcomeTokenAmounts outcomeTokenMarginalPrices outcomes + usdLiquidityMeasure } } """ diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py index 32f39a02..9f2b664e 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py @@ -20,7 +20,7 @@ import json from abc import ABC from enum import Enum, auto -from typing import Any, Dict, Generator, Iterator, List, Optional, Tuple, Union, cast +from typing import Any, Dict, Generator, Iterator, List, Optional, Tuple, cast from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.models import ApiSpecs @@ -94,7 +94,7 @@ def current_subgraph(self) -> ApiSpecs: def _prepare_bets_fetching(self) -> bool: """Prepare for fetching a bet.""" if self._fetch_status in (FetchStatus.SUCCESS, FetchStatus.NONE): - res = next(self._creators_iterator) + res = next(self._creators_iterator, None) if res is None: return False self._current_market, self._current_creators = next(self._creators_iterator) @@ -109,14 +109,12 @@ def _handle_response( self, res: Optional[Dict], res_context: str, - keys: Tuple[Union[str, int], ...], sleep_on_fail: bool = True, ) -> Generator[None, None, Optional[Any]]: """Handle a response from a subgraph. :param res: the response to handle. :param res_context: the context of the current response. - :param keys: keys to get the information from the response. :param sleep_on_fail: whether we want to sleep if we fail to get the response's result. :return: the response's result, using the given keys. `None` if response is `None` (has failed). :yield: None @@ -136,18 +134,13 @@ def _handle_response( yield from self.sleep(sleep_time) return None - value = res[keys[0]] - if len(keys) > 1: - for key in keys[1:]: - value = value[key] - - self.context.logger.info(f"Retrieved {res_context}: {value}.") + self.context.logger.info(f"Retrieved {res_context}: {res}.") self._call_failed = False self.current_subgraph.reset_retries() self._fetch_status = FetchStatus.SUCCESS - return value + return res - def _fetch_bets(self) -> Generator[None, None, Optional[dict]]: + def _fetch_bets(self) -> Generator[None, None, Optional[list]]: """Fetch questions from the current subgraph, for the current creators.""" self._fetch_status = FetchStatus.IN_PROGRESS @@ -167,7 +160,6 @@ def _fetch_bets(self) -> Generator[None, None, Optional[dict]]: bets = yield from self._handle_response( res, res_context="questions", - keys=("fixedProductMarketMakers",), ) return bets diff --git a/packages/valory/skills/market_manager_abci/models.py b/packages/valory/skills/market_manager_abci/models.py index 8f0c4211..ee4797bb 100644 --- a/packages/valory/skills/market_manager_abci/models.py +++ b/packages/valory/skills/market_manager_abci/models.py @@ -30,6 +30,7 @@ from packages.valory.skills.abstract_round_abci.models import ( SharedState as BaseSharedState, ) +from packages.valory.skills.market_manager_abci.bets import BINARY_N_SLOTS from packages.valory.skills.market_manager_abci.rounds import MarketManagerAbciApp @@ -57,6 +58,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "creator_per_subgraph", kwargs, Dict[str, List[str]] ) self.slot_count: int = self._ensure("slot_count", kwargs, int) + + if self.slot_count != BINARY_N_SLOTS: + raise ValueError( + f"Only a slot_count `2` is currently supported. `{self.slot_count}` was found in the configuration." + ) + self.opening_margin: int = self._ensure("opening_margin", kwargs, int) self.languages: List[str] = self._ensure("languages", kwargs, List[str]) super().__init__(*args, **kwargs) diff --git a/packages/valory/skills/market_manager_abci/rounds.py b/packages/valory/skills/market_manager_abci/rounds.py index e8ff47f6..dfb88626 100644 --- a/packages/valory/skills/market_manager_abci/rounds.py +++ b/packages/valory/skills/market_manager_abci/rounds.py @@ -61,12 +61,12 @@ def _get_deserialized(self, key: str) -> DeserializedCollection: return CollectionRound.deserialize_collection(serialized) @property - def bets(self) -> Dict[str, List[Bet]]: + def bets(self) -> List[Bet]: """Get the most voted bets.""" serialized_bets = str(self.db.get("bets", "")) if serialized_bets == "": - return {} + return [] return json.loads(serialized_bets, cls=BetsDecoder) @@ -97,9 +97,9 @@ class UpdateBetsRound(CollectSameUntilThresholdRound, MarketManagerAbstractRound """A round for the bets fetching & updating.""" payload_class = UpdateBetsPayload - done_event = Event.DONE - none_event = Event.FETCH_ERROR - no_majority_event = Event.NO_MAJORITY + done_event: Enum = Event.DONE + none_event: Enum = Event.FETCH_ERROR + no_majority_event: Enum = Event.NO_MAJORITY selection_key = get_name(SynchronizedData.bets) collection_key = get_name(SynchronizedData.participant_to_bets) synchronized_data_class = SynchronizedData diff --git a/packages/valory/skills/market_manager_abci/skill.yaml b/packages/valory/skills/market_manager_abci/skill.yaml index 26e922fd..3363b960 100644 --- a/packages/valory/skills/market_manager_abci/skill.yaml +++ b/packages/valory/skills/market_manager_abci/skill.yaml @@ -10,7 +10,8 @@ fingerprint: fingerprint_ignore_patterns: [] connections: [] contracts: [] -protocols: [] +protocols: +- valory/http:1.0.0:bafybeifyoio7nlh5zzyn5yz7krkou56l22to3cwg7gw5v5o3vxwklibhty skills: - valory/abstract_round_abci:0.1.0:bafybeiac62ennpw54gns2quk4g3yoaili2mb72nj6c52czobz5dcwj4mwi behaviours: @@ -136,7 +137,7 @@ models: Content-Type: application/json method: POST parameters: { } - response_key: data + response_key: data:fixedProductMarketMakers response_type: dict retries: 5 url: https://api.thegraph.com/subgraphs/name/protofire/omen-xdai diff --git a/poetry.lock b/poetry.lock index 7e03f40a..e0eac89d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,6 +101,18 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "base58" version = "2.1.1" @@ -1302,7 +1314,6 @@ files = [ [package.dependencies] click = ">=8.0" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.0" Jinja2 = ">=3.0" Werkzeug = ">=2.0" @@ -1336,14 +1347,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.91.0" +version = "2.92.0" description = "Google API Client Library for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-python-client-2.91.0.tar.gz", hash = "sha256:d9385ad6e7f95eecd40f7c81e3abfe4b6ad3a84f2c16bcdb66fb7b8dd814ed56"}, - {file = "google_api_python_client-2.91.0-py2.py3-none-any.whl", hash = "sha256:6959d21d4b20c0f65c69662ca7b6a8a02fc08f3e7f72d70b28ae3e6e3a5f9ab2"}, + {file = "google-api-python-client-2.92.0.tar.gz", hash = "sha256:f38a6e106a7417719715506d36f0a233ec253335e422bda311352866a86c4187"}, + {file = "google_api_python_client-2.92.0-py2.py3-none-any.whl", hash = "sha256:e0b74ed5fa9bdb07a66fb030d3f4cae550ed1c07e23600d86450d3c3c5efae51"}, ] [package.dependencies] @@ -1414,6 +1425,45 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "gql" +version = "3.4.1" +description = "GraphQL client for Python" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "gql-3.4.1-py2.py3-none-any.whl", hash = "sha256:315624ca0f4d571ef149d455033ebd35e45c1a13f18a059596aeddcea99135cf"}, + {file = "gql-3.4.1.tar.gz", hash = "sha256:11dc5d8715a827f2c2899593439a4f36449db4f0eafa5b1ea63948f8a2f8c545"}, +] + +[package.dependencies] +backoff = ">=1.11.1,<3.0" +graphql-core = ">=3.2,<3.3" +yarl = ">=1.6,<2.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.7.1,<3.9.0)"] +all = ["aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +botocore = ["botocore (>=1.21,<2)"] +dev = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "sphinx (>=3.0.0,<4)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)"] +test = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.0.2)"] +websockets = ["websockets (>=10,<11)", "websockets (>=9,<10)"] + +[[package]] +name = "graphql-core" +version = "3.2.3" +description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." +category = "main" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, + {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, +] + [[package]] name = "grpcio" version = "1.43.0" @@ -1552,26 +1602,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1807,6 +1837,27 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "mech-client" +version = "0.2.2" +description = "Basic client to interact with a mech" +category = "main" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "mech_client-0.2.2-py3-none-any.whl", hash = "sha256:deaafea15d1dee0e4ed56124a325dbf27bb65c826c6ae0c6093cf718ad51dbe0"}, + {file = "mech_client-0.2.2.tar.gz", hash = "sha256:2704a9f50e8da467942f9e4ef2dcfb5d05c58d2a9fe2f460da2af78b87c471f8"}, +] + +[package.dependencies] +asn1crypto = ">=1.4.0,<1.5.0" +gql = ">=3.4.1" +open-aea = {version = "1.35.0", extras = ["cli"]} +open-aea-cli-ipfs = "1.35.0" +open-aea-ledger-cosmos = "1.35.0" +open-aea-ledger-ethereum = "1.35.0" +websocket-client = ">=0.32.0,<1" + [[package]] name = "morphys" version = "1.0" @@ -1944,44 +1995,6 @@ files = [ {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, ] -[[package]] -name = "numpy" -version = "1.24.4" -description = "Fundamental package for array computing in Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, -] - [[package]] name = "numpy" version = "1.25.0" @@ -2035,8 +2048,8 @@ files = [ [package.dependencies] base58 = ">=1.0.3,<3.0.0" -click = {version = "8.0.2", optional = true, markers = "extra == \"all\""} -coverage = {version = ">=6.4.4,<8.0.0", optional = true, markers = "extra == \"all\""} +click = {version = "8.0.2", optional = true, markers = "extra == \"cli\""} +coverage = {version = ">=6.4.4,<8.0.0", optional = true, markers = "extra == \"cli\""} ecdsa = ">=0.15,<0.17.0" jsonschema = ">=3.0.0,<4.0.0" morphys = ">=1.0" @@ -2045,7 +2058,7 @@ protobuf = ">=3.19.0,<4.0.0" py-multibase = ">=1.0.0" py-multicodec = ">=0.2.0" pymultihash = "0.8.2" -pytest = {version = ">=7.0.0,<7.3.0", optional = true, markers = "extra == \"all\""} +pytest = {version = ">=7.0.0,<7.3.0", optional = true, markers = "extra == \"cli\""} python-dotenv = ">=0.14.0,<0.18.0" pyyaml = ">=4.2b1,<6.0" requests = ">=2.22.0,<3.0.0" @@ -2209,12 +2222,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.17.3", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, - {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, -] +numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""} python-dateutil = ">=2.7.3" pytz = ">=2017.3" @@ -2259,14 +2267,14 @@ six = ">=1.9.0" [[package]] name = "platformdirs" -version = "3.8.0" +version = "3.8.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, - {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"}, + {file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"}, ] [package.extras] @@ -2735,7 +2743,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" [[package]] @@ -3456,23 +3463,7 @@ files = [ idna = ">=2.0" multidict = ">=4.0" -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "2.0" -python-versions = "<3.11, >=3.8" -content-hash = "25bb044bb01a4defbd16557242c92586ba1dd5592523fdde3c5dc9ff58a167e6" +python-versions = ">=3.10, <3.11" +content-hash = "9b90524176d2146d43ae1306618b4800b462913d469cb1b12f95eaf5d661de03" diff --git a/pyproject.toml b/pyproject.toml index 1aad5967..591d3928 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Environment :: Console", "Environment :: Web Environment", "Dev include = "packages" [tool.poetry.dependencies] -python = "<3.11, >=3.8" +python = ">=3.10, <3.11" open-autonomy = "==0.10.7" requests = "==2.28.2" py-multibase = "==1.0.3" @@ -27,6 +27,7 @@ open-aea-ledger-ethereum = "==1.35.0" open-aea-ledger-cosmos = "*" protobuf = "<=3.20.1,>=3.19" hypothesis = "==6.80.0" +mech-client = "==0.2.2" open-aea-test-autonomy = "==0.10.7" web3 = "==5.31.4" ipfshttpclient = "==0.8.0a2" diff --git a/tox.ini b/tox.ini index 3ba7c9c9..a2b34b49 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,7 @@ deps = typing_extensions>=3.10.0.2 websocket_client<1,>=0.32.0 toml==0.10.2 + mech-client==0.2.2 [testenv] basepython = python3 @@ -58,7 +59,7 @@ setenv = PYTHONPATH={env:PWD:%CD%} PACKAGES_PATHS = packages/valory SKILLS_PATHS = {env:PACKAGES_PATHS}/skills - SERVICE_SPECIFIC_PACKAGES = {env:SKILLS_PATHS}/market_manager_abci + SERVICE_SPECIFIC_PACKAGES = {env:SKILLS_PATHS}/market_manager_abci {env:SKILLS_PATHS}/decision_maker_abci [testenv:bandit] skipsdist = True @@ -334,6 +335,9 @@ ignore_missing_imports = True [mypy-aea_cli_ipfs.*] ignore_missing_imports = True +[mypy-mech_client.*] +ignore_missing_imports = True + [mypy-py_eth_sig_utils.*] ignore_missing_imports = True