diff --git a/.gitleaksignore b/.gitleaksignore index 05a4d32..5be9bfa 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -251,3 +251,4 @@ bea8fa71765fab49fd7b0a7b99e27019b5df0d5e:scripts/api_data.json:generic-api-key:2 bea8fa71765fab49fd7b0a7b99e27019b5df0d5e:scripts/api_data.json:generic-api-key:21 5a9e025d6b0cd4115123d890ec90cd69a8fc682f:scripts/api_data.json:generic-api-key:20 5a9e025d6b0cd4115123d890ec90cd69a8fc682f:scripts/api_data.json:generic-api-key:21 +92a4c419cc5a67f25f5768cc985c73b80114314c:packages/valory/contracts/staking_token/contract.yaml:generic-api-key:10 diff --git a/packages/packages.json b/packages/packages.json index b19b740..e9e596e 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -6,12 +6,12 @@ "contract/valory/uniswap_v3_non_fungible_position_manager/0.1.0": "bafybeieljamerttxyo7z2yokwripnnhzkn4zply5lz457vsixf5wfu5px4", "contract/valory/uniswap_v3_pool/0.1.0": "bafybeidglijnyueahpgivaykbhio2r3ovfeo23a256y3yb6g7be4hngx3a", "contract/valory/merkl_distributor/0.1.0": "bafybeifctofnyhdic2sxmkqujvf3j2wwydhtvzhi6kdeutykenymplf4e4", - "contract/valory/staking_token/0.1.0": "bafybeiakvgm2byyjfunb4npthioku2kwqpbcwm6dfov4akirqixtju6oai", - "contract/valory/staking_activity_checker/0.1.0": "bafybeigfiqmvzsf2etp6grtnfqg4y7nrlhwohbhfvggewhgvvolascs4fe", - "skill/valory/liquidity_trader_abci/0.1.0": "bafybeieoj2cuuwk32settpv44ailcyrd4lh7mj6exr5ufssnms3teyhxze", - "skill/valory/optimus_abci/0.1.0": "bafybeigfneogfptzhfovlqy5r57rlei7b6ixe2k37ke6xrv6lr76kcviai", - "agent/valory/optimus/0.1.0": "bafybeibubmgk63swmozdglm3hmu4n3bz2xesxggdxdtx6cuso7feh3a7xu", - "service/valory/optimus/0.1.0": "bafybeie4mwft76qkajsn3wypza5vcpvx5vdaosywqzzsnohr4my6o2xu3y" + "contract/valory/staking_token/0.1.0": "bafybeihgp74ojttyzuriukd44biv2ehh4rcc3czm7mv2olw7amepxszbzu", + "contract/valory/staking_activity_checker/0.1.0": "bafybeibfqnnqgrchsykidx3x3fgwjmjnml7jyl6e66prhxars3gzgosvxq", + "skill/valory/liquidity_trader_abci/0.1.0": "bafybeihii4e64akqkelii37havxmet3qeg5vlv4vg2toekoaww6tk7zudi", + "skill/valory/optimus_abci/0.1.0": "bafybeiazrizmyz5rbqkodjo3jrregmeoyg2qrov4p5qyh5znggeg2kmtdi", + "agent/valory/optimus/0.1.0": "bafybeicjbstfud6oljhtbobgahpzujyigo2u3eit62srji2ik52irhdv7i", + "service/valory/optimus/0.1.0": "bafybeibjwknk7bchs24irn7ayogp72i2cbaioqcd5dzssqtdq4gihrocu4" }, "third_party": { "protocol/open_aea/signing/1.0.0": "bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi", diff --git a/packages/valory/agents/optimus/aea-config.yaml b/packages/valory/agents/optimus/aea-config.yaml index 125e4de..e85d67a 100644 --- a/packages/valory/agents/optimus/aea-config.yaml +++ b/packages/valory/agents/optimus/aea-config.yaml @@ -35,8 +35,8 @@ protocols: skills: - valory/abstract_abci:0.1.0:bafybeihu2bcgjk2tqjiq2zhk3uogtfszqn4osvdt7ho3fubdpdj4jgdfjm - valory/abstract_round_abci:0.1.0:bafybeibovsktd3uxur45nrcomq5shcn46cgxd5idmhxbmjhg32c5abyqim -- valory/liquidity_trader_abci:0.1.0:bafybeieoj2cuuwk32settpv44ailcyrd4lh7mj6exr5ufssnms3teyhxze -- valory/optimus_abci:0.1.0:bafybeigfneogfptzhfovlqy5r57rlei7b6ixe2k37ke6xrv6lr76kcviai +- valory/liquidity_trader_abci:0.1.0:bafybeihii4e64akqkelii37havxmet3qeg5vlv4vg2toekoaww6tk7zudi +- valory/optimus_abci:0.1.0:bafybeiazrizmyz5rbqkodjo3jrregmeoyg2qrov4p5qyh5znggeg2kmtdi - valory/registration_abci:0.1.0:bafybeicnth5q4httefsusywx3zrrq4al47owvge72dqf2fziruicq6hqta - valory/reset_pause_abci:0.1.0:bafybeievjciqdvxhqxfjd4whqs27h6qbxqzrae7wwj7fpvxlvmtw3x35im - valory/termination_abci:0.1.0:bafybeid54buqxipiuduw7b6nnliiwsxajnltseuroad53wukfonpxca2om diff --git a/packages/valory/contracts/staking_activity_checker/contract.py b/packages/valory/contracts/staking_activity_checker/contract.py index a20c644..9b251bf 100644 --- a/packages/valory/contracts/staking_activity_checker/contract.py +++ b/packages/valory/contracts/staking_activity_checker/contract.py @@ -42,3 +42,16 @@ def liveness_ratio( contract = cls.get_instance(ledger_api, contract_address) liveness_ratio = contract.functions.livenessRatio().call() return dict(data=liveness_ratio) + + @classmethod + def get_multisig_nonces( + cls, + ledger_api: LedgerApi, + contract_address: str, + multisig: str, + ) -> JSONLike: + """Retrieve the nonces for a given multisig address.""" + contract = cls.get_instance(ledger_api, contract_address) + nonces = contract.functions.getMultisigNonces(multisig).call() + return dict(data=nonces) + diff --git a/packages/valory/contracts/staking_activity_checker/contract.yaml b/packages/valory/contracts/staking_activity_checker/contract.yaml index 7fc6152..451fcbb 100644 --- a/packages/valory/contracts/staking_activity_checker/contract.yaml +++ b/packages/valory/contracts/staking_activity_checker/contract.yaml @@ -8,7 +8,7 @@ aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: bafybeigqm6q7kgfzgpcse7zer5uea3s7myolry2r3teovuzqgkn43j7bei build/StakingActivityChecker.json: bafybeiety3kkkmdz2gdzktg54wvmhavftaw4wjnrddztt64jpknagrc6xa - contract.py: bafybeihnil43tcs2jhii5gxhsffzknsvljnnhradi62pnxhs525orc2cw4 + contract.py: bafybeialo57soqxxxozi545hb3gqtf644qr2od4gskexdhhv6nmwpw6ktu fingerprint_ignore_patterns: [] contracts: [] class_name: StakingActivityCheckerContract diff --git a/packages/valory/services/optimus/service.yaml b/packages/valory/services/optimus/service.yaml index fe159a1..d223902 100644 --- a/packages/valory/services/optimus/service.yaml +++ b/packages/valory/services/optimus/service.yaml @@ -6,7 +6,7 @@ aea_version: '>=1.0.0, <2.0.0' license: Apache-2.0 fingerprint: {} fingerprint_ignore_patterns: [] -agent: valory/optimus:0.1.0:bafybeibubmgk63swmozdglm3hmu4n3bz2xesxggdxdtx6cuso7feh3a7xu +agent: valory/optimus:0.1.0:bafybeicjbstfud6oljhtbobgahpzujyigo2u3eit62srji2ik52irhdv7i number_of_agents: 1 deployment: {} --- @@ -68,7 +68,7 @@ models: service_endpoint_base: ${SERVICE_ENDPOINT_BASE:str:https://optimism.autonolas.tech/} multisend_batch_size: ${MULTISEND_BATCH_SIZE:int:5} safe_contract_addresses: ${SAFE_CONTRACT_ADDRESSES:str:{"ethereum":"0x0000000000000000000000000000000000000000","base":"0x0000000000000000000000000000000000000000","optimism":"0x0000000000000000000000000000000000000000"}} - staking_token_contract_address: ${STAKING_TOKEN_CONTRACT_ADDRESS:str:0x63C2c53c09dE534Dd3bc0b7771bf976070936bAC} + staking_token_contract_address: ${STAKING_TOKEN_CONTRACT_ADDRESS:str:0x88996bbdE7f982D93214881756840cE2c77C4992} staking_activity_checker_contract_address: ${STAKING_ACTIVITY_CHECKER_CONTRACT_ADDRESS:str:0x7Fd1F4b764fA41d19fe3f63C85d12bf64d2bbf68} staking_threshold_period: ${STAKING_THRESHOLD_PERIOD:int:5} store_path: ${STORE_PATH:str:/data/} diff --git a/packages/valory/skills/liquidity_trader_abci/behaviours.py b/packages/valory/skills/liquidity_trader_abci/behaviours.py index 8065027..ccdd5c3 100644 --- a/packages/valory/skills/liquidity_trader_abci/behaviours.py +++ b/packages/valory/skills/liquidity_trader_abci/behaviours.py @@ -90,6 +90,13 @@ SAFE_TX_GAS = 0 ETHER_VALUE = 0 +# Liveness ratio from the staking contract is expressed in calls per 10**18 seconds. +LIVENESS_RATIO_SCALE_FACTOR = 10**18 + +# A safety margin in case there is a delay between the moment the KPI condition is +# satisfied, and the moment where the checkpoint is called. +REQUIRED_REQUESTS_SAFETY_MARGIN = 1 + WaitableConditionType = Generator[None, None, Any] @@ -150,6 +157,7 @@ def __init__(self, **kwargs: Any) -> None: self.pools: Dict[str, Any] = {} self.pools[DexTypes.BALANCER.value] = BalancerPoolBehaviour self.pools[DexTypes.UNISWAP_V3.value] = UniswapPoolBehaviour + self.service_staking_state = None self.strategy = SimpleStrategyBehaviour # Read the assets and current pool self.read_current_pool() @@ -389,70 +397,6 @@ def read_current_pool(self) -> None: """Read the current pool as JSON.""" self._read_data("current_pool", self.current_pool_filepath) - -class CallCheckpointBehaviour( - LiquidityTraderBaseBehaviour -): # pylint-disable too-many-ancestors - matching_round = CallCheckpointRound - - def async_act(self) -> Generator: - """Do the action.""" - with self.context.benchmark_tool.measure(self.behaviour_id).local(): - service_staking_state = yield from self._get_service_staking_state( - chain="optimism" - ) - checkpoint_tx_hex = None - min_num_of_safe_tx_required = None - - if service_staking_state == StakingState.STAKED: - is_checkpoint_reached = yield from self._check_if_checkpoint_reached( - chain="optimism" - ) - if is_checkpoint_reached: - self.context.logger.info( - "Checkpoint reached! Preparing checkpoint tx.." - ) - checkpoint_tx_hex = yield from self._prepare_checkpoint_tx( - chain="optimism" - ) - - min_num_of_safe_tx_required = ( - yield from self._calculate_min_num_of_safe_tx_required( - chain="optimism" - ) - ) - self.context.logger.info( - f"The minimum number of safe tx required to unlock rewards are {min_num_of_safe_tx_required}" - ) - - self.shared_state.last_checkpoint_executed_period_number = ( - self.synchronized_data.period_count - ) - - elif service_staking_state == StakingState.EVICTED: - self.context.logger.error("Service has been evicted!") - - else: - self.context.logger.error("Service has not been staked") - - tx_submitter = self.matching_round.auto_round_id() - payload = CallCheckpointPayload( - self.context.agent_address, - tx_submitter, - service_staking_state.value, - min_num_of_safe_tx_required, - checkpoint_tx_hex, - safe_contract_address=self.params.safe_contract_addresses.get( - "optimism" - ), - chain_id="optimism", - ) - - 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() - def _calculate_min_num_of_safe_tx_required( self, chain: str ) -> Generator[None, None, Optional[int]]: @@ -463,13 +407,46 @@ def _calculate_min_num_of_safe_tx_required( if not liveness_ratio or not liveness_period: return None - # Calculate the minimum number of transactions - min_num_of_safe_tx_required = math.ceil( - liveness_ratio * liveness_period // 10**18 + current_timestamp = int( + self.round_sequence.last_round_transition_timestamp.timestamp() + ) + + last_ts_checkpoint = yield from self._get_ts_checkpoint(chain="optimism") + min_num_of_safe_tx_required = ( + math.ceil( + max(liveness_period, (current_timestamp - last_ts_checkpoint)) + * liveness_ratio + // LIVENESS_RATIO_SCALE_FACTOR + ) + + REQUIRED_REQUESTS_SAFETY_MARGIN ) return min_num_of_safe_tx_required + def _get_next_checkpoint(self, chain: str) -> Generator[None, None, Optional[int]]: + """Get the timestamp in which the next checkpoint is reached.""" + next_checkpoint = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, + contract_address=self.params.staking_token_contract_address, + contract_public_id=StakingTokenContract.contract_id, + contract_callable="get_next_checkpoint_ts", + data_key="data", + chain_id=chain, + ) + return next_checkpoint + + def _get_ts_checkpoint(self, chain: str) -> Generator[None, None, Optional[int]]: + """Get the ts checkpoint""" + ts_checkpoint = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, + contract_address=self.params.staking_token_contract_address, + contract_public_id=StakingTokenContract.contract_id, + contract_callable="ts_checkpoint", + data_key="data", + chain_id=chain, + ) + return ts_checkpoint + def _get_liveness_ratio(self, chain: str) -> Generator[None, None, Optional[int]]: liveness_ratio = yield from self.contract_interact( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, @@ -504,16 +481,101 @@ def _get_liveness_period(self, chain: str) -> Generator[None, None, Optional[int return liveness_period - def _get_service_staking_state( + def _is_staking_kpi_met(self) -> Generator[None, None, Optional[bool]]: + """Return whether the staking KPI has been met (only for staked services).""" + if self.service_staking_state is None: + yield from self._get_service_staking_state(chain="optimism") + + if self.service_staking_state != StakingState.STAKED: + return False + + min_num_of_safe_tx_required = self.synchronized_data.min_num_of_safe_tx_required + if min_num_of_safe_tx_required is None: + min_num_of_safe_tx_required = yield from self._calculate_min_num_of_safe_tx_required(chain="optimism") + + if min_num_of_safe_tx_required is None: + self.context.logger.error( + "Error calculating min number of safe tx required." + ) + return None + + multisig_nonces_since_last_cp = yield from self._get_multisig_nonces_since_last_cp( + chain="optimism", + multisig=self.params.safe_contract_addresses.get("optimism"), + ) + if ( + multisig_nonces_since_last_cp + and multisig_nonces_since_last_cp >= min_num_of_safe_tx_required + ): + return True + + return False + + def _get_multisig_nonces( + self, chain: str, multisig: str + ) -> Generator[None, None, Optional[int]]: + multisig_nonces = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, + contract_address=self.params.staking_activity_checker_contract_address, + contract_public_id=StakingActivityCheckerContract.contract_id, + contract_callable="get_multisig_nonces", + data_key="data", + chain_id=chain, + multisig=multisig, + ) + return multisig_nonces[0] + + def _get_multisig_nonces_since_last_cp( + self, chain: str, multisig: str + ) -> Generator[None, None, Optional[int]]: + multisig_nonces = yield from self._get_multisig_nonces(chain, multisig) + service_info = yield from self._get_service_info(chain) + if service_info is None or len(service_info) == 0 or len(service_info[2]) == 0: + self.context.logger.error(f"Error fetching service info {service_info}") + return None + + multisig_nonces_on_last_checkpoint = service_info[2][0] + + multisig_nonces_since_last_cp = ( + multisig_nonces - multisig_nonces_on_last_checkpoint + ) + self.context.logger.info( + f"Multisig nonces since last checkpoint: {multisig_nonces_since_last_cp}" + ) + return multisig_nonces_since_last_cp + + def _get_service_info( self, chain: str - ) -> Generator[None, None, StakingState]: + ) -> Generator[None, None, Optional[Tuple[Any, Any, Tuple[Any, Any]]]]: + """Get the service info.""" service_id = self.params.on_chain_service_id if service_id is None: self.context.logger.warning( "Cannot perform any staking-related operations without a configured on-chain service id. " "Assuming service status 'UNSTAKED'." ) - return StakingState.UNSTAKED + return None + + service_info = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, + contract_address=self.params.staking_token_contract_address, + contract_public_id=StakingTokenContract.contract_id, + contract_callable="get_service_info", + data_key="data", + service_id=service_id, + chain_id=chain, + ) + return service_info + + def _get_service_staking_state(self, chain: str) -> Generator[None, None, None]: + service_id = self.params.on_chain_service_id + if service_id is None: + self.context.logger.warning( + "Cannot perform any staking-related operations without a configured on-chain service id. " + "Assuming service status 'UNSTAKED'." + ) + self.service_staking_state = StakingState.UNSTAKED + return service_staking_state = yield from self.contract_interact( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, @@ -529,9 +591,72 @@ def _get_service_staking_state( "Error fetching staking state for service." "Assuming service status 'UNSTAKED'." ) - return StakingState.UNSTAKED + self.service_staking_state = StakingState.UNSTAKED + return + + self.service_staking_state = StakingState(service_staking_state) + return + + +class CallCheckpointBehaviour( + LiquidityTraderBaseBehaviour +): # pylint-disable too-many-ancestors + matching_round = CallCheckpointRound + + def async_act(self) -> Generator: + """Do the action.""" + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + yield from self._get_service_staking_state(chain="optimism") + checkpoint_tx_hex = None + min_num_of_safe_tx_required = None + is_staking_kpi_met = False + if self.service_staking_state == StakingState.STAKED: + min_num_of_safe_tx_required = yield from self._calculate_min_num_of_safe_tx_required( + chain="optimism" + ) + self.context.logger.info( + f"The minimum number of safe tx required to unlock rewards are {min_num_of_safe_tx_required}" + ) + is_checkpoint_reached = yield from self._check_if_checkpoint_reached( + chain="optimism" + ) + if is_checkpoint_reached: + self.context.logger.info( + "Checkpoint reached! Preparing checkpoint tx.." + ) + checkpoint_tx_hex = yield from self._prepare_checkpoint_tx( + chain="optimism" + ) + is_staking_kpi_met = False + else: + is_staking_kpi_met = yield from self._is_staking_kpi_met() + if is_staking_kpi_met is None: + self.service_staking_state = StakingState.UNSTAKED + + elif self.service_staking_state == StakingState.EVICTED: + self.context.logger.error("Service has been evicted!") + + else: + self.context.logger.error("Service has not been staked") + + tx_submitter = self.matching_round.auto_round_id() + payload = CallCheckpointPayload( + self.context.agent_address, + tx_submitter, + self.service_staking_state.value, + min_num_of_safe_tx_required, + is_staking_kpi_met, + checkpoint_tx_hex, + safe_contract_address=self.params.safe_contract_addresses.get( + "optimism" + ), + chain_id="optimism", + ) - return StakingState(service_staking_state) + 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() def _check_if_checkpoint_reached( self, chain: str @@ -548,18 +673,6 @@ def _check_if_checkpoint_reached( ) return next_checkpoint <= synced_timestamp - def _get_next_checkpoint(self, chain: str) -> Generator[None, None, Optional[int]]: - """Get the timestamp in which the next checkpoint is reached.""" - next_checkpoint = yield from self.contract_interact( - performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, - contract_address=self.params.staking_token_contract_address, - contract_public_id=StakingTokenContract.contract_id, - contract_callable="get_next_checkpoint_ts", - data_key="data", - chain_id=chain, - ) - return next_checkpoint - def _prepare_checkpoint_tx( self, chain: str ) -> Generator[None, None, Optional[str]]: @@ -601,7 +714,6 @@ def _prepare_safe_tx( safe_tx_hash=safe_tx_hash, ether_value=ETHER_VALUE, safe_tx_gas=SAFE_TX_GAS, - operation=SafeOperation.CALL.value, to_address=self.params.staking_token_contract_address, data=data, ) @@ -615,9 +727,10 @@ def async_act(self) -> Generator: """Do the action.""" with self.context.benchmark_tool.measure(self.behaviour_id).local(): vanity_tx_hex = None - kpi_met_for_the_day = self.synchronized_data.kpi_met_for_the_day - - if not kpi_met_for_the_day: + is_staking_kpi_met = yield from self._is_staking_kpi_met() + if is_staking_kpi_met: + self.context.logger.info("KPI already met for the day!") + else: last_round_id = ( self.context.state.round_sequence._abci_app._previous_rounds[ -1 @@ -629,38 +742,52 @@ def async_act(self) -> Generator: and self.synchronized_data.tx_submitter != CallCheckpointRound.auto_round_id() ) - is_period_threshold_exceeded = self._check_period_threshold_exceeded() + is_period_threshold_exceeded = ( + self.synchronized_data.period_count + >= self.params.staking_threshold_period + ) if is_post_tx_settlement_round or is_period_threshold_exceeded: min_num_of_safe_tx_required = ( self.synchronized_data.min_num_of_safe_tx_required ) - if min_num_of_safe_tx_required is None: + if not min_num_of_safe_tx_required: + min_num_of_safe_tx_required = ( + yield from self._calculate_min_num_of_safe_tx_required( + chain="optimism" + ) + ) + + multisig_nonces_since_last_cp = ( + yield from self._get_multisig_nonces_since_last_cp( + chain="optimism", + multisig=self.params.safe_contract_addresses.get( + "optimism" + ), + ) + ) + + num_of_tx_left_to_meet_kpi = ( + min_num_of_safe_tx_required - multisig_nonces_since_last_cp + ) + if num_of_tx_left_to_meet_kpi > 0: self.context.logger.info( - f"Invalid value for minimum number of safe tx: {min_num_of_safe_tx_required}" + f"Number of tx left to meet KPI: {num_of_tx_left_to_meet_kpi}" ) - else: - num_of_tx_left_to_meet_kpi = ( - min_num_of_safe_tx_required - - self.synchronized_data.curr_num_of_safe_tx + self.context.logger.info(f"Preparing vanity tx..") + vanity_tx_hex = yield from self._prepare_vanity_tx( + chain="optimism" ) - if num_of_tx_left_to_meet_kpi > 0: - self.context.logger.info( - f"KPI not hit for the day, preparing vanity tx.." - ) - vanity_tx_hex = yield from self._prepare_vanity_tx( - chain="optimism" - ) - self.context.logger.info(f"tx hash: {vanity_tx_hex}") - else: - kpi_met_for_the_day = True - self.context.logger.info(f"KPI met for the day!") + self.context.logger.info(f"tx hash: {vanity_tx_hex}") + else: + is_staking_kpi_met = True + self.context.logger.info("KPI met for the day!") tx_submitter = self.matching_round.auto_round_id() payload = CheckStakingKPIMetPayload( self.context.agent_address, tx_submitter, - kpi_met_for_the_day, + is_staking_kpi_met, vanity_tx_hex, safe_contract_address=self.params.safe_contract_addresses.get( "optimism" @@ -673,16 +800,6 @@ def async_act(self) -> Generator: yield from self.wait_until_round_end() self.set_done() - def _check_period_threshold_exceeded(self) -> Generator[None, None, bool]: - if not self.shared_state.last_checkpoint_executed_period_number: - return False - - elapsed_periods = ( - self.synchronized_data.period_count - - self.shared_state.last_checkpoint_executed_period_number - ) - return elapsed_periods >= self.params.staking_threshold_period - def _prepare_vanity_tx(self, chain: str) -> Generator[None, None, Optional[str]]: safe_address = self.params.safe_contract_addresses.get(chain) tx_data = b"0x" diff --git a/packages/valory/skills/liquidity_trader_abci/fsm_specification.yaml b/packages/valory/skills/liquidity_trader_abci/fsm_specification.yaml index 2f0efbb..89d93d2 100644 --- a/packages/valory/skills/liquidity_trader_abci/fsm_specification.yaml +++ b/packages/valory/skills/liquidity_trader_abci/fsm_specification.yaml @@ -13,6 +13,9 @@ alphabet_in: - UNRECOGNIZED - UPDATE - WAIT +- STAKING_KPI_NOT_MET +- STAKING_KPI_MET +- WAIT_FOR_PERIODS_TO_PASS default_start_state: CallCheckpointRound final_states: - FailedMultiplexerRound @@ -49,10 +52,13 @@ transition_func: (CallCheckpointRound, SETTLE): FinishedCallCheckpointRound (CallCheckpointRound, SERVICE_EVICTED): GetPositionsRound (CallCheckpointRound, SERVICE_NOT_STAKED): GetPositionsRound - (CheckStakingKPIMetRound, DONE): GetPositionsRound + (CallCheckpointRound, STAKING_KPI_NOT_MET): CheckStakingKPIMetRound + (CallCheckpointRound, STAKING_KPI_MET): GetPositionsRound + (CheckStakingKPIMetRound, STAKING_KPI_MET): GetPositionsRound (CheckStakingKPIMetRound, NO_MAJORITY): CheckStakingKPIMetRound (CheckStakingKPIMetRound, ROUND_TIMEOUT): CheckStakingKPIMetRound (CheckStakingKPIMetRound, SETTLE): FinishedCheckStakingKPIMetRound + (CheckStakingKPIMetRound, WAIT_FOR_PERIODS_TO_PASS): GetPositionsRound (DecisionMakingRound, DONE): FinishedDecisionMakingRound (DecisionMakingRound, ERROR): FinishedDecisionMakingRound (DecisionMakingRound, NO_MAJORITY): DecisionMakingRound @@ -67,7 +73,7 @@ transition_func: (GetPositionsRound, NO_MAJORITY): GetPositionsRound (GetPositionsRound, ROUND_TIMEOUT): GetPositionsRound (PostTxSettlementRound, ACTION_EXECUTED): DecisionMakingRound - (PostTxSettlementRound, CALLPOINT_TX_EXECUTED): CheckStakingKPIMetRound + (PostTxSettlementRound, CALLPOINT_TX_EXECUTED): CallCheckpointRound (PostTxSettlementRound, VANITY_TX_EXECUTED): CheckStakingKPIMetRound (PostTxSettlementRound, ROUND_TIMEOUT): PostTxSettlementRound (PostTxSettlementRound, UNRECOGNIZED): FailedMultiplexerRound diff --git a/packages/valory/skills/liquidity_trader_abci/models.py b/packages/valory/skills/liquidity_trader_abci/models.py index 0504839..5dbfa79 100644 --- a/packages/valory/skills/liquidity_trader_abci/models.py +++ b/packages/valory/skills/liquidity_trader_abci/models.py @@ -45,8 +45,6 @@ class SharedState(BaseSharedState): def __init__(self, *args: Any, skill_context: SkillContext, **kwargs: Any) -> None: """Initialize the state.""" super().__init__(*args, skill_context=skill_context, **kwargs) - # represents the period number at which last checkpoint tx was executed - self.last_checkpoint_executed_period_number: Optional[int] = None Requests = BaseRequests diff --git a/packages/valory/skills/liquidity_trader_abci/payloads.py b/packages/valory/skills/liquidity_trader_abci/payloads.py index 1602efc..f6ae3db 100644 --- a/packages/valory/skills/liquidity_trader_abci/payloads.py +++ b/packages/valory/skills/liquidity_trader_abci/payloads.py @@ -32,6 +32,7 @@ class CallCheckpointPayload(BaseTxPayload): tx_submitter: str service_staking_state: int min_num_of_safe_tx_required: Optional[int] + is_staking_kpi_met: Optional[bool] tx_hash: Optional[str] safe_contract_address: Optional[str] chain_id: Optional[str] @@ -42,7 +43,7 @@ class CheckStakingKPIMetPayload(BaseTxPayload): """A transaction payload for the CheckStakingKPIMetRound.""" tx_submitter: str - kpi_met_for_the_day: Optional[bool] + is_staking_kpi_met: Optional[bool] tx_hash: Optional[str] safe_contract_address: Optional[str] chain_id: Optional[str] diff --git a/packages/valory/skills/liquidity_trader_abci/rounds.py b/packages/valory/skills/liquidity_trader_abci/rounds.py index f036eed..4653ab5 100644 --- a/packages/valory/skills/liquidity_trader_abci/rounds.py +++ b/packages/valory/skills/liquidity_trader_abci/rounds.py @@ -69,6 +69,9 @@ class Event(Enum): UPDATE = "update" VANITY_TX_EXECUTED = "vanity_tx_executed" WAIT = "wait" + STAKING_KPI_NOT_MET = "staking_kpi_not_met" + STAKING_KPI_MET = "staking_kpi_met" + WAIT_FOR_PERIODS_TO_PASS = "wait_for_periods_to_pass" class SynchronizedData(BaseSynchronizedData): @@ -151,16 +154,6 @@ def min_num_of_safe_tx_required(self) -> Optional[int]: """Get the min number of safe tx required.""" return cast(int, self.db.get("min_num_of_safe_tx_required")) - @property - def curr_num_of_safe_tx(self) -> int: - """Get the current number of safe tx.""" - return cast(int, self.db.get("curr_num_of_safe_tx", 0)) - - @property - def total_num_of_safe_tx(self) -> int: - """Get the total number of safe tx.""" - return cast(int, self.db.get("total_num_of_safe_tx", 0)) - @property def participant_to_checkpoint(self) -> DeserializedCollection: """Get the participants to the checkpoint round.""" @@ -172,10 +165,15 @@ def participant_to_staking_kpi(self) -> DeserializedCollection: return self._get_deserialized("participant_to_staking_kpi") @property - def kpi_met_for_the_day(self) -> Optional[bool]: + def is_staking_kpi_met(self) -> Optional[bool]: """Get kpi met for the day.""" - return cast(int, self.db.get("kpi_met_for_the_day", False)) + return cast(int, self.db.get("is_staking_kpi_met", False)) + @property + def chain_id(self) -> Optional[str]: + """Get the chain id.""" + return cast(str, self.db.get("chain_id", None)) + class CallCheckpointRound(CollectSameUntilThresholdRound): """A round for the checkpoint call preparation.""" @@ -187,8 +185,10 @@ class CallCheckpointRound(CollectSameUntilThresholdRound): get_name(SynchronizedData.tx_submitter), get_name(SynchronizedData.service_staking_state), get_name(SynchronizedData.min_num_of_safe_tx_required), + get_name(SynchronizedData.is_staking_kpi_met), get_name(SynchronizedData.most_voted_tx_hash), get_name(SynchronizedData.safe_contract_address), + get_name(SynchronizedData.chain_id), ) collection_key = get_name(SynchronizedData.participant_to_checkpoint) synchronized_data_class = SynchronizedData @@ -216,8 +216,17 @@ def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: if synced_data.service_staking_state == StakingState.EVICTED.value: return synced_data, Event.SERVICE_EVICTED - if synced_data.most_voted_tx_hash is None: - return synced_data, Event.NEXT_CHECKPOINT_NOT_REACHED_YET + if ( + synced_data.most_voted_tx_hash is None + and synced_data.is_staking_kpi_met == False + ): + return synced_data, Event.STAKING_KPI_NOT_MET + + if ( + synced_data.most_voted_tx_hash is None + and synced_data.is_staking_kpi_met == True + ): + return synced_data, Event.STAKING_KPI_MET return res @@ -232,9 +241,10 @@ class CheckStakingKPIMetRound(CollectSameUntilThresholdRound): collection_key = get_name(SynchronizedData.participant_to_staking_kpi) selection_key = ( get_name(SynchronizedData.tx_submitter), - get_name(SynchronizedData.kpi_met_for_the_day), + get_name(SynchronizedData.is_staking_kpi_met), get_name(SynchronizedData.most_voted_tx_hash), get_name(SynchronizedData.safe_contract_address), + get_name(SynchronizedData.chain_id), ) def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: @@ -248,14 +258,14 @@ def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: if event != Event.DONE: return res - if synced_data.kpi_met_for_the_day == True: - return synced_data, Event.DONE + if synced_data.is_staking_kpi_met == True: + return synced_data, Event.STAKING_KPI_MET else: if synced_data.most_voted_tx_hash is not None: return synced_data, Event.SETTLE else: - return synced_data, Event.DONE + return synced_data, Event.WAIT_FOR_PERIODS_TO_PASS class GetPositionsRound(CollectSameUntilThresholdRound): @@ -369,20 +379,6 @@ def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: synced_data = SynchronizedData(self.synchronized_data.db) event = submitter_to_event.get(synced_data.tx_submitter, Event.UNRECOGNIZED) - # if a checkpoint tx was just executed, we reset the current number of safe tx and kpi met to False - if event == Event.CHECKPOINT_TX_EXECUTED: - self.synchronized_data.update( - curr_num_of_safe_tx=0, kpi_met_for_the_day=False - ) - - # if any tx was executed we update the current safe tx count and total safe tx count - if event != Event.UNRECOGNIZED: - updated_curr_num_of_safe_tx = synced_data.curr_num_of_safe_tx + 1 - updated_total_num_of_safe_tx = synced_data.total_num_of_safe_tx + 1 - self.synchronized_data.update( - curr_num_of_safe_tx=updated_curr_num_of_safe_tx, - total_num_of_safe_tx=updated_total_num_of_safe_tx, - ) return synced_data, event @@ -428,15 +424,17 @@ class LiquidityTraderAbciApp(AbciApp[Event]): Event.SETTLE: FinishedCallCheckpointRound, Event.SERVICE_NOT_STAKED: GetPositionsRound, Event.SERVICE_EVICTED: GetPositionsRound, - Event.NEXT_CHECKPOINT_NOT_REACHED_YET: GetPositionsRound, Event.ROUND_TIMEOUT: CallCheckpointRound, Event.NO_MAJORITY: CallCheckpointRound, + Event.STAKING_KPI_NOT_MET: CheckStakingKPIMetRound, + Event.STAKING_KPI_MET: GetPositionsRound, }, CheckStakingKPIMetRound: { - Event.DONE: GetPositionsRound, + Event.STAKING_KPI_MET: GetPositionsRound, Event.SETTLE: FinishedCheckStakingKPIMetRound, Event.ROUND_TIMEOUT: CheckStakingKPIMetRound, Event.NO_MAJORITY: CheckStakingKPIMetRound, + Event.WAIT_FOR_PERIODS_TO_PASS: GetPositionsRound, }, GetPositionsRound: { Event.DONE: EvaluateStrategyRound, @@ -459,7 +457,7 @@ class LiquidityTraderAbciApp(AbciApp[Event]): }, PostTxSettlementRound: { Event.ACTION_EXECUTED: DecisionMakingRound, - Event.CHECKPOINT_TX_EXECUTED: CheckStakingKPIMetRound, + Event.CHECKPOINT_TX_EXECUTED: CallCheckpointRound, Event.VANITY_TX_EXECUTED: CheckStakingKPIMetRound, Event.ROUND_TIMEOUT: PostTxSettlementRound, Event.UNRECOGNIZED: FailedMultiplexerRound, @@ -486,9 +484,7 @@ class LiquidityTraderAbciApp(AbciApp[Event]): { get_name(SynchronizedData.last_reward_claimed_timestamp), get_name(SynchronizedData.min_num_of_safe_tx_required), - get_name(SynchronizedData.curr_num_of_safe_tx), - get_name(SynchronizedData.total_num_of_safe_tx), - get_name(SynchronizedData.kpi_met_for_the_day), + get_name(SynchronizedData.is_staking_kpi_met), } ) db_pre_conditions: Dict[AppState, Set[str]] = { diff --git a/packages/valory/skills/liquidity_trader_abci/skill.yaml b/packages/valory/skills/liquidity_trader_abci/skill.yaml index 9210d18..ee2d93f 100644 --- a/packages/valory/skills/liquidity_trader_abci/skill.yaml +++ b/packages/valory/skills/liquidity_trader_abci/skill.yaml @@ -7,16 +7,16 @@ license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: bafybeia7bn2ahqqwkf63ptje6rfnftuwrsp33sswgpcbh5osbesxxr6g4m - behaviours.py: bafybeig6rcjcpbr3hr2o4rx45ven3ok7zxn6bkq6acbb3uw6dfiwwzmvse + behaviours.py: bafybeicm2544nuorncipsbt4estpihctu66jh5qbzsnmsiggxstx7obe2e dialogues.py: bafybeiay23otskx2go5xhtgdwfw2kd6rxd62sxxdu3njv7hageorl5zxzm - fsm_specification.yaml: bafybeia7u62xbzkllitosyelsur3tmmet7k63lb6xzjdpqreb67siljbiu + fsm_specification.yaml: bafybeif4v5cy62pcxtilwbxnxr3hbiwmwlmw2xucc6wl3paa6rsqxlndmm handlers.py: bafybeidxw2lvgiifmo4siobpwuwbxscuifrdo3gnkjyn6bgexotj5f7zf4 - models.py: bafybeibc4okx7cd5nsf7soq6vxt2prvp3y56gwqjufeng4pzwnfehvqiwa - payloads.py: bafybeif2n5mdjv2vo7vulfxwgzchqyvklok746davqwwbwlmfd5xoycaku + models.py: bafybeicu6nuwivewuhjvoq6p362rz5bvwtchqss5ce6b32km2brvybuzdi + payloads.py: bafybeigi7t4fe3gr4reuq2d2xz4qblbxlo6wwy2hwh7htaaihjoya7uqky pool_behaviour.py: bafybeiaheuesscgqzwjbpyrezgwpdbdfurlmfwbc462qv6rblwwxlx5dpm pools/balancer.py: bafybeieo53qtt557632vtp3x3huagdpqevptextbwd7euk6hpoz6ybtuci pools/uniswap.py: bafybeif2cjbtjh5altlgranmgrif4yaevnn344fn3askbjde5h4y4rh2mq - rounds.py: bafybeidqfjrqznv2e6jjvnb76exzksuef5nof4ebvz2rsv4rxgciqjf2pu + rounds.py: bafybeid7jpwkznn5nmsqnspc6ykhfddwujmdxwkqmwjchmw6k7jjqgwg6e strategies/simple_strategy.py: bafybeig2iygxz5gewmiyeawubs5f4pr5e3st22lmdkxthlfq7t5kbluw4a strategy_behaviour.py: bafybeidk6sorg47kuuubamcccksi65x3txldyo7y2hm5opbye2ghmz2ljy fingerprint_ignore_patterns: [] @@ -30,8 +30,8 @@ contracts: - valory/uniswap_v3_non_fungible_position_manager:0.1.0:bafybeieljamerttxyo7z2yokwripnnhzkn4zply5lz457vsixf5wfu5px4 - valory/uniswap_v3_pool:0.1.0:bafybeidglijnyueahpgivaykbhio2r3ovfeo23a256y3yb6g7be4hngx3a - valory/merkl_distributor:0.1.0:bafybeifctofnyhdic2sxmkqujvf3j2wwydhtvzhi6kdeutykenymplf4e4 -- valory/staking_token:0.1.0:bafybeiakvgm2byyjfunb4npthioku2kwqpbcwm6dfov4akirqixtju6oai -- valory/staking_activity_checker:0.1.0:bafybeigfiqmvzsf2etp6grtnfqg4y7nrlhwohbhfvggewhgvvolascs4fe +- valory/staking_token:0.1.0:bafybeihgp74ojttyzuriukd44biv2ehh4rcc3czm7mv2olw7amepxszbzu +- valory/staking_activity_checker:0.1.0:bafybeibfqnnqgrchsykidx3x3fgwjmjnml7jyl6e66prhxars3gzgosvxq protocols: - valory/contract_api:1.0.0:bafybeidgu7o5llh26xp3u3ebq3yluull5lupiyeu6iooi2xyymdrgnzq5i - valory/ledger_api:1.0.0:bafybeihdk6psr4guxmbcrc26jr2cbgzpd5aljkqvpwo64bvaz7tdti2oni diff --git a/packages/valory/skills/optimus_abci/skill.yaml b/packages/valory/skills/optimus_abci/skill.yaml index 6ec9ed4..5530918 100644 --- a/packages/valory/skills/optimus_abci/skill.yaml +++ b/packages/valory/skills/optimus_abci/skill.yaml @@ -8,11 +8,11 @@ aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: bafybeiechr3zr5bc4xl3vvs2p2gti54ily7ao2gu4ff5lys6cixehzkdea behaviours.py: bafybeig3agtm256pl7rekx3i3kbz6h5hrnneu44f4pzkqp4mwtdxuob54e - composition.py: bafybeieentvbw64iagpiytcugj552klqpbw2bllemk6fqb6vjxuuvo3n24 + composition.py: bafybeigxpycqh7xnqwdj5eg2ln6kqchbwvwuvzjtpwhgylr6qb56ooo7wu dialogues.py: bafybeiafoomno5pn6qrx43jxf2opxkil5eg4nod6jhd5oqwwplfz4x6dke - fsm_specification.yaml: bafybeidf2q5f32qajgahjqkn4l7gjb2gawsner6k3ihaqc2nuevybaainm + fsm_specification.yaml: bafybeia5uzg27tczkp3gw6k73zrpnnesbdzhpjt6ylyxfpvbarlixmueme handlers.py: bafybeife4nrwqiwrx2ucza3vk6t5inpkncuewehtdnitax4lmqq2ptoona - models.py: bafybeicaoh25u5tacnjyexzxwl4ojqmcllqfhntz2ecr35uz7rjkaam3vu + models.py: bafybeihvuvhlmumkoqhhnw3bw4ixwvjkb3gwkgnu653ggj3zpqahyyasui fingerprint_ignore_patterns: [] connections: [] contracts: [] @@ -22,7 +22,7 @@ skills: - valory/registration_abci:0.1.0:bafybeicnth5q4httefsusywx3zrrq4al47owvge72dqf2fziruicq6hqta - valory/reset_pause_abci:0.1.0:bafybeievjciqdvxhqxfjd4whqs27h6qbxqzrae7wwj7fpvxlvmtw3x35im - valory/termination_abci:0.1.0:bafybeid54buqxipiuduw7b6nnliiwsxajnltseuroad53wukfonpxca2om -- valory/liquidity_trader_abci:0.1.0:bafybeieoj2cuuwk32settpv44ailcyrd4lh7mj6exr5ufssnms3teyhxze +- valory/liquidity_trader_abci:0.1.0:bafybeihii4e64akqkelii37havxmet3qeg5vlv4vg2toekoaww6tk7zudi - valory/transaction_settlement_abci:0.1.0:bafybeihq2yenstblmaadzcjousowj5kfn5l7ns5pxweq2gcrsczfyq5wzm behaviours: main: