diff --git a/packages/packages.json b/packages/packages.json index 6ec90b679..b190dd212 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -17,13 +17,13 @@ "contract/valory/relayer/0.1.0": "bafybeihzgjyvhtorugjw3yldznqsbwo3aqpxowm7k2nrvj6qtwpsc7jl7u", "skill/valory/market_manager_abci/0.1.0": "bafybeiai6djelf6d4dkxgkv46l24q2gz7736b3jdhbxslvcydpvnvrse6e", "skill/valory/decision_maker_abci/0.1.0": "bafybeigh2ajmwscmgvl3t4wkwyeclszs5ujykm5omget7yap7zmwironkq", - "skill/valory/trader_abci/0.1.0": "bafybeie3njujc5zdwqncwwr3yhfm33cggtihxwrdjlzpgaom4zdnidy6ka", + "skill/valory/trader_abci/0.1.0": "bafybeifjeeulkss2c4dnhzs24u63556w2vq3xxbfykxn7hcl7wirwvjqcq", "skill/valory/tx_settlement_multiplexer_abci/0.1.0": "bafybeibyverkghpxz7di3wgxdixwcofrxec35b4tb4ac5pebpseax76eby", "skill/valory/staking_abci/0.1.0": "bafybeiduborfqevheegy3plk7bzhkl4fukwixvlb57tenijdepintubbdi", - "skill/valory/check_stop_trading_abci/0.1.0": "bafybeiepylk35n3faurvp7dskjkdovehftzfjrjxfkpekzuaovt5gojxne", - "agent/valory/trader/0.1.0": "bafybeiexvarbsj25xevrtuqavwmgpvmz753a74im7su7jzamhhkuqjcl3y", - "service/valory/trader/0.1.0": "bafybeighrps33nlltr7gwdeaftkii3t2itwshvk5ulpazjyxdmed6gmbo4", - "service/valory/trader_pearl/0.1.0": "bafybeihqtqgohejyebrb4jvd2fgccuasmqk3gc456ylyfa7tcn6rucdrxm" + "skill/valory/check_stop_trading_abci/0.1.0": "bafybeiat5hmo6i2r6t6ufyfnidvepixmbnppotdmztu4dad5j4hiewdh64", + "agent/valory/trader/0.1.0": "bafybeibwds53c4mtfnrymruw56o4nzmtv4zwu7nbhbxi7ix2ra4izk6tx4", + "service/valory/trader/0.1.0": "bafybeigwmrak2lnnzjx3o3la6jaxgp7tlm4wr4lks3movgveqgvwou7s7a", + "service/valory/trader_pearl/0.1.0": "bafybeigwl2i45vuovylfaymuid73x4c2efwkwcbau7dthv3k7raohfgqqy" }, "third_party": { "protocol/open_aea/signing/1.0.0": "bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi", diff --git a/packages/valory/agents/trader/aea-config.yaml b/packages/valory/agents/trader/aea-config.yaml index 7456bfb44..e729218cf 100644 --- a/packages/valory/agents/trader/aea-config.yaml +++ b/packages/valory/agents/trader/aea-config.yaml @@ -50,9 +50,9 @@ skills: - valory/tx_settlement_multiplexer_abci:0.1.0:bafybeibyverkghpxz7di3wgxdixwcofrxec35b4tb4ac5pebpseax76eby - valory/market_manager_abci:0.1.0:bafybeiai6djelf6d4dkxgkv46l24q2gz7736b3jdhbxslvcydpvnvrse6e - valory/decision_maker_abci:0.1.0:bafybeigh2ajmwscmgvl3t4wkwyeclszs5ujykm5omget7yap7zmwironkq -- valory/trader_abci:0.1.0:bafybeie3njujc5zdwqncwwr3yhfm33cggtihxwrdjlzpgaom4zdnidy6ka +- valory/trader_abci:0.1.0:bafybeifjeeulkss2c4dnhzs24u63556w2vq3xxbfykxn7hcl7wirwvjqcq - valory/staking_abci:0.1.0:bafybeiduborfqevheegy3plk7bzhkl4fukwixvlb57tenijdepintubbdi -- valory/check_stop_trading_abci:0.1.0:bafybeiepylk35n3faurvp7dskjkdovehftzfjrjxfkpekzuaovt5gojxne +- valory/check_stop_trading_abci:0.1.0:bafybeiat5hmo6i2r6t6ufyfnidvepixmbnppotdmztu4dad5j4hiewdh64 - valory/mech_interact_abci:0.1.0:bafybeih2cck5xu6yaibomwtm5zbcp6llghr3ighdnk56fzwu3ihu5xx35e customs: - valory/mike_strat:0.1.0:bafybeihjiol7f4ch4piwfikurdtfwzsh6qydkbsztpbwbwb2yrqdqf726m diff --git a/packages/valory/services/trader/service.yaml b/packages/valory/services/trader/service.yaml index 83489f3df..4957ba9fa 100644 --- a/packages/valory/services/trader/service.yaml +++ b/packages/valory/services/trader/service.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeigtuothskwyvrhfosps2bu6suauycolj67dpuxqvnicdrdu7yhtvq fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeiexvarbsj25xevrtuqavwmgpvmz753a74im7su7jzamhhkuqjcl3y +agent: valory/trader:0.1.0:bafybeibwds53c4mtfnrymruw56o4nzmtv4zwu7nbhbxi7ix2ra4izk6tx4 number_of_agents: 4 deployment: agent: diff --git a/packages/valory/services/trader_pearl/service.yaml b/packages/valory/services/trader_pearl/service.yaml index 98aed8b8c..b427fa875 100644 --- a/packages/valory/services/trader_pearl/service.yaml +++ b/packages/valory/services/trader_pearl/service.yaml @@ -8,7 +8,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeibg7bdqpioh4lmvknw3ygnllfku32oca4eq5pqtvdrdsgw6buko7e fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeiexvarbsj25xevrtuqavwmgpvmz753a74im7su7jzamhhkuqjcl3y +agent: valory/trader:0.1.0:bafybeibwds53c4mtfnrymruw56o4nzmtv4zwu7nbhbxi7ix2ra4izk6tx4 number_of_agents: 1 deployment: agent: diff --git a/packages/valory/skills/check_stop_trading_abci/skill.yaml b/packages/valory/skills/check_stop_trading_abci/skill.yaml index 46f4a866d..82a7943dd 100644 --- a/packages/valory/skills/check_stop_trading_abci/skill.yaml +++ b/packages/valory/skills/check_stop_trading_abci/skill.yaml @@ -16,8 +16,10 @@ fingerprint: payloads.py: bafybeidh5bqywun4chrbsci2xbcrnnzuys5sswxwbxq3yl2ksawi3xsi5q rounds.py: bafybeigqkzikghmzjj2ceqrnvmiiagtris3livgvn6r5z5ossk73xcfqfy tests/__init__.py: bafybeihv2cjk4va5bc5ncqtppqg2xmmxcro34bma36trtvk32gtmhdycxu + tests/test_dialogues.py: bafybeia5ac27w7ijx2nyx5dqyrnv4troo4572gjq7nrcxdncexoxucnqti tests/test_handlers.py: bafybeigpmtx2hyunzn6nxk2x4bvvybek7jvuhbk34fqlj7fgfsszcoqhxy tests/test_payloads.py: bafybeih7q7kdfxsf4ejxxqwjumwglfwwcrbqcjnuy42mkhnfwccxuhiviy + tests/test_rounds.py: bafybeidgbc7mi7r2fpk7ak6xceohuoq2zkpkberkokcb3sb2uzwkxoluae fingerprint_ignore_patterns: [] connections: [] contracts: diff --git a/packages/valory/skills/check_stop_trading_abci/tests/test_dialogues.py b/packages/valory/skills/check_stop_trading_abci/tests/test_dialogues.py new file mode 100644 index 000000000..be3bcac1f --- /dev/null +++ b/packages/valory/skills/check_stop_trading_abci/tests/test_dialogues.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2024 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. +# +# ------------------------------------------------------------------------------ + +"""Test the dialogues.py module of the skill.""" + +# pylint: skip-file + +import packages.valory.skills.check_stop_trading_abci.dialogues # noqa + + +def test_import() -> None: + """Test that the 'dialogues.py' Python module can be imported.""" diff --git a/packages/valory/skills/check_stop_trading_abci/tests/test_rounds.py b/packages/valory/skills/check_stop_trading_abci/tests/test_rounds.py new file mode 100644 index 000000000..8967831fa --- /dev/null +++ b/packages/valory/skills/check_stop_trading_abci/tests/test_rounds.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 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 tests for the CheckStopTradingAbciApp.""" + +import json +from dataclasses import dataclass, field +from typing import ( + Any, + Callable, + Dict, + FrozenSet, + Hashable, + List, + Mapping, + Optional, + Type, +) +from unittest.mock import MagicMock, Mock + +import pytest + +from packages.valory.skills.abstract_round_abci.base import ( + AbciAppDB, + CollectionRound, + VotingRound, + get_name, +) +from packages.valory.skills.abstract_round_abci.test_tools.rounds import ( + BaseVotingRoundTest, +) +from packages.valory.skills.check_stop_trading_abci.payloads import ( + CheckStopTradingPayload, +) +from packages.valory.skills.check_stop_trading_abci.rounds import ( + CheckStopTradingAbciApp, + CheckStopTradingRound, + Event, + FinishedCheckStopTradingRound, + FinishedWithSkipTradingRound, + SynchronizedData, +) + + +DUMMY_PAYLOAD_DATA = {"example_key": "example_value"} + + +@pytest.fixture +def abci_app() -> CheckStopTradingAbciApp: + """Fixture for CheckStopTradingAbciApp.""" + synchronized_data = Mock() + logger = Mock() + context = Mock() + + return CheckStopTradingAbciApp( + synchronized_data=synchronized_data, logger=logger, context=context + ) + + +def get_participants() -> FrozenSet[str]: + """Participants""" + return frozenset([f"agent_{i}" for i in range(MAX_PARTICIPANTS)]) + + +def get_participant_to_votes( + participants: FrozenSet[str], vote: bool +) -> Dict[str, CheckStopTradingPayload]: + """participant_to_votes""" + + return { + participant: CheckStopTradingPayload(sender=participant, vote=vote) + for participant in participants + } + + +def get_participant_to_votes_serialized( + participants: FrozenSet[str], vote: bool +) -> Dict[str, Dict[str, Any]]: + """participant_to_votes""" + + return CollectionRound.serialize_collection( + get_participant_to_votes(participants, vote) + ) + + +def get_payloads( + payload_cls: Type[CheckStopTradingPayload], + data: Optional[str], +) -> Mapping[str, CheckStopTradingPayload]: + """Get payloads.""" + return { + participant: payload_cls(participant, data is not None) + for participant in get_participants() + } + + +def get_dummy_check_stop_trading_payload_serialized() -> str: + """Dummy payload serialization""" + return json.dumps(DUMMY_PAYLOAD_DATA, sort_keys=True) + + +@dataclass +class RoundTestCase: + """RoundTestCase""" + + name: str + initial_data: Dict[str, Hashable] + payloads: Mapping[str, CheckStopTradingPayload] + final_data: Dict[str, Hashable] + event: Event + most_voted_payload: Any + synchronized_data_attr_checks: List[Callable] = field(default_factory=list) + + +MAX_PARTICIPANTS: int = 4 + + +class BaseCheckStopTradingRoundTest(BaseVotingRoundTest): + """Base Test Class for CheckStopTradingRound""" + + test_class: Type[VotingRound] + test_payload: Type[CheckStopTradingPayload] + + def _test_voting_round( + self, vote: bool, expected_event: Any, threshold_check: Callable + ) -> None: + """Helper method to test voting rounds with positive or negative votes.""" + + test_round = self.test_class( + synchronized_data=self.synchronized_data, context=MagicMock() + ) + + self._complete_run( + self._test_round( + test_round=test_round, + round_payloads=get_participant_to_votes(self.participants, vote=vote), + synchronized_data_update_fn=lambda _synchronized_data, _: _synchronized_data.update( + participant_to_votes=get_participant_to_votes_serialized( + self.participants, vote=vote + ) + ), + synchronized_data_attr_checks=[ + lambda _synchronized_data: _synchronized_data.participant_to_votes.keys() + ] + if vote + else [], + exit_event=expected_event, + threshold_check=threshold_check, + ) + ) + + def test_positive_votes(self) -> None: + """Test ValidateRound for positive votes.""" + self._test_voting_round( + vote=True, + expected_event=self._event_class.SKIP_TRADING, + threshold_check=lambda x: x.positive_vote_threshold_reached, + ) + + def test_negative_votes(self) -> None: + """Test ValidateRound for negative votes.""" + self._test_voting_round( + vote=False, + expected_event=self._event_class.DONE, + threshold_check=lambda x: x.negative_vote_threshold_reached, + ) + + +class TestCheckStopTradingRound(BaseCheckStopTradingRoundTest): + """Tests for CheckStopTradingRound.""" + + test_class = CheckStopTradingRound + _event_class = Event + _synchronized_data_class = SynchronizedData + + @pytest.mark.parametrize( + "test_case", + ( + RoundTestCase( + name="Happy path", + initial_data={}, + payloads=get_payloads( + payload_cls=CheckStopTradingPayload, + data=get_dummy_check_stop_trading_payload_serialized(), + ), + final_data={}, + event=Event.SKIP_TRADING, + most_voted_payload=get_dummy_check_stop_trading_payload_serialized(), + synchronized_data_attr_checks=[ + lambda sync_data: sync_data.db.get( + get_name(SynchronizedData.participant_to_votes) + ) + == CollectionRound.deserialize_collection( + json.loads(get_dummy_check_stop_trading_payload_serialized()) + ) + ], + ), + RoundTestCase( + name="No majority", + initial_data={}, + payloads=get_payloads( + payload_cls=CheckStopTradingPayload, + data=get_dummy_check_stop_trading_payload_serialized(), + ), + final_data={}, + event=Event.NO_MAJORITY, + most_voted_payload=get_dummy_check_stop_trading_payload_serialized(), + synchronized_data_attr_checks=[], + ), + ), + ) + def test_run(self, test_case: RoundTestCase) -> None: + """Run tests.""" + if test_case.event == Event.SKIP_TRADING: + self.test_positive_votes() + elif test_case.event == Event.NO_MAJORITY: + self.test_negative_votes() + + """Tests for FinishedCheckStopTradingRound.""" + + def test_finished_check_stop_trading_round_initialization(self) -> None: + """Test the initialization of FinishedCheckStopTradingRound.""" + round_ = FinishedCheckStopTradingRound( + synchronized_data=MagicMock(), context=MagicMock() + ) + assert isinstance(round_, FinishedCheckStopTradingRound) + + +class TestFinishedWithSkipTradingRound: + """Tests for FinishedWithSkipTradingRound.""" + + def test_finished_with_skip_trading_round_initialization(self) -> None: + """Test the initialization of FinishedWithSkipTradingRound.""" + round_ = FinishedWithSkipTradingRound( + synchronized_data=MagicMock(), context=MagicMock() + ) + assert isinstance(round_, FinishedWithSkipTradingRound) + + +def test_abci_app_initialization(abci_app: CheckStopTradingAbciApp) -> None: + """Test the initialization of CheckStopTradingAbciApp.""" + assert abci_app.initial_round_cls is CheckStopTradingRound + assert abci_app.final_states == { + FinishedCheckStopTradingRound, + FinishedWithSkipTradingRound, + } + assert abci_app.transition_function == { + CheckStopTradingRound: { + Event.DONE: FinishedCheckStopTradingRound, + Event.NONE: CheckStopTradingRound, + Event.ROUND_TIMEOUT: CheckStopTradingRound, + Event.NO_MAJORITY: CheckStopTradingRound, + Event.SKIP_TRADING: FinishedWithSkipTradingRound, + }, + FinishedCheckStopTradingRound: {}, + FinishedWithSkipTradingRound: {}, + } + assert abci_app.event_to_timeout == {Event.ROUND_TIMEOUT: 30.0} + assert abci_app.db_pre_conditions == {CheckStopTradingRound: set()} + assert abci_app.db_post_conditions == { + FinishedCheckStopTradingRound: set(), + FinishedWithSkipTradingRound: set(), + } + + +def test_synchronized_data_initialization() -> None: + """Test the initialization and attributes of SynchronizedData.""" + data = SynchronizedData(db=AbciAppDB(setup_data={"test": ["test"]})) + assert data.db._data == {0: {"test": ["test"]}} diff --git a/packages/valory/skills/trader_abci/skill.yaml b/packages/valory/skills/trader_abci/skill.yaml index e4a457a69..db01e5186 100644 --- a/packages/valory/skills/trader_abci/skill.yaml +++ b/packages/valory/skills/trader_abci/skill.yaml @@ -30,7 +30,7 @@ skills: - valory/decision_maker_abci:0.1.0:bafybeigh2ajmwscmgvl3t4wkwyeclszs5ujykm5omget7yap7zmwironkq - valory/tx_settlement_multiplexer_abci:0.1.0:bafybeibyverkghpxz7di3wgxdixwcofrxec35b4tb4ac5pebpseax76eby - valory/staking_abci:0.1.0:bafybeiduborfqevheegy3plk7bzhkl4fukwixvlb57tenijdepintubbdi -- valory/check_stop_trading_abci:0.1.0:bafybeiepylk35n3faurvp7dskjkdovehftzfjrjxfkpekzuaovt5gojxne +- valory/check_stop_trading_abci:0.1.0:bafybeiat5hmo6i2r6t6ufyfnidvepixmbnppotdmztu4dad5j4hiewdh64 - valory/mech_interact_abci:0.1.0:bafybeih2cck5xu6yaibomwtm5zbcp6llghr3ighdnk56fzwu3ihu5xx35e behaviours: main: