From 2b67c0ac6b5b3e3a645e469c007c6e4ee1660d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Mon, 28 Apr 2025 12:44:21 +0000 Subject: [PATCH 01/17] Add new exception to CLI: * CLICannotPerformTransactionError - raise when cannot perform transaction. --- clive/__private/cli/exceptions.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clive/__private/cli/exceptions.py b/clive/__private/cli/exceptions.py index d3a27e977e..236b5efcf3 100644 --- a/clive/__private/cli/exceptions.py +++ b/clive/__private/cli/exceptions.py @@ -282,3 +282,12 @@ class CLITransactionBadAccountError(CLIPrettyError): f"target accounts: {self.account_names} are on the list of bad accounts." ) super().__init__(message, errno.EINVAL) + + +class CLITransactionToExchangeError(CLIPrettyError): + """Raise when trying to perform transaction to exchange with operation(s) that cannot be performed.""" + + def __init__(self, reason: str) -> None: + message = f"Cannot perform transaction.\n{reason}" + super().__init__(message, errno.EINVAL) + self.message = message -- GitLab From eece909dff6728663716a68b08017a582cbbe76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Mon, 2 Jun 2025 08:44:36 +0000 Subject: [PATCH 02/17] Add as_markup to humanize_validation_result. --- clive/__private/core/formatters/humanize.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clive/__private/core/formatters/humanize.py b/clive/__private/core/formatters/humanize.py index bf761aa015..3570cf88d2 100644 --- a/clive/__private/core/formatters/humanize.py +++ b/clive/__private/core/formatters/humanize.py @@ -85,12 +85,14 @@ def align_to_dot(*strings: str, center_to: int | str | None = None) -> list[str] return aligned_strings -def humanize_validation_result(result: ValidationResult) -> str: +def humanize_validation_result(result: ValidationResult, *, as_markup: bool = False) -> str: """Return failure description from ValidationResult if any exists, otherwise returns 'No failures' message.""" if result.is_valid: return "No failures" if len(result.failure_descriptions) > 1: + if as_markup: + return "\n".join(f"{desc}" for desc in result.failure_descriptions) return str(result.failure_descriptions) return result.failure_descriptions[0] -- GitLab From cd2d818d5f76b8d4812376877e617ae70bee8305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Tue, 29 Apr 2025 12:53:50 +0000 Subject: [PATCH 03/17] Add PotentialExchangeOperationsAccountCollector - it collects accounts with operations potentially problematic for exchanges (memoless, HBD, force-required) --- ...l_exchange_operations_account_collector.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 clive/__private/visitors/operation/potential_exchange_operations_account_collector.py diff --git a/clive/__private/visitors/operation/potential_exchange_operations_account_collector.py b/clive/__private/visitors/operation/potential_exchange_operations_account_collector.py new file mode 100644 index 0000000000..5943a23877 --- /dev/null +++ b/clive/__private/visitors/operation/potential_exchange_operations_account_collector.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, override + +if TYPE_CHECKING: + from clive.__private.models import schemas +from clive.__private.models.asset import Asset +from clive.__private.visitors.operation.financial_operations_account_collector import ( + FinancialOperationsAccountCollector, +) + + +class PotentialExchangeOperationsAccountCollector(FinancialOperationsAccountCollector): + """ + Collects exchange accounts with potentially problematic operations to them. + + Can be used to check if memoless/HBD transfer was detected and + if there is any other unsafe operations (other than transfer). + """ + + def __init__(self) -> None: + super().__init__() + self.memoless_transfers_accounts: set[str] = set() + """Names of accounts with memoless transfers.""" + self.hbd_transfers_accounts: set[str] = set() + """Names of accounts with hbd transfers.""" + + def has_hbd_transfer_operations_to_exchange(self, account: str) -> bool: + """Check if there are HBD transfer operations to the given exchange account.""" + return account in self.hbd_transfers_accounts + + def has_memoless_transfer_operations_to_exchange(self, account: str) -> bool: + """Check if there are memoless transfer operations to the given exchange account.""" + return account in self.memoless_transfers_accounts + + def has_unsafe_operation_to_exchange(self, account: str) -> bool: + """ + Check if there are unsafe operations to the given exchange account. + + Transfer is the only operation considered a safe operation to the exchange account. + """ + return account in self.accounts + + @override + def visit_transfer_operation(self, operation: schemas.TransferOperation) -> None: + """ + Collect memoless and hbd transfers account names. + + Transfer to exchange should have asset in Hive and memo. + + If: + 1) the memo is empty string, it is considered a memoless transfer. + 2) the amount is Asset.Hbd, this is forbidden operation to exchange. + """ + if operation.memo == "": + self.memoless_transfers_accounts.add(operation.to) + return + if isinstance(operation.amount, Asset.Hbd): + self.hbd_transfers_accounts.add(operation.to) -- GitLab From b1497be7c1db81d6da3cdfe87a2d467ae2523359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Mon, 26 May 2025 08:55:28 +0000 Subject: [PATCH 04/17] Add ExchangeOperationsValidator - it will check if transaction has memoless transfer or force required operation to exchange. --- .../exchange_operations_validator.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 clive/__private/validators/exchange_operations_validator.py diff --git a/clive/__private/validators/exchange_operations_validator.py b/clive/__private/validators/exchange_operations_validator.py new file mode 100644 index 0000000000..a4b31bce98 --- /dev/null +++ b/clive/__private/validators/exchange_operations_validator.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Final + +from textual.validation import Function, ValidationResult, Validator + +from clive.__private.visitors.operation.potential_exchange_operations_account_collector import ( + PotentialExchangeOperationsAccountCollector, +) + +if TYPE_CHECKING: + from typing import ClassVar + + from clive.__private.models import Transaction + + +class ExchangeOperationsValidator(Validator): + """Validating operations in a transaction to exchange.""" + + HBD_TRANSFER_MSG_ERROR: Final[str] = "The transfer to the exchange must be in HIVE, not HBD." + MEMOLESS_HIVE_TRANSFER_MSG_ERROR: Final[str] = "The transfer to the exchange must include a memo." + + UNSAFE_EXCHANGE_OPERATION_MSG_ERROR: ClassVar[str] = ( + "Exchanges usually support only the transfer operation, while other operation to a known exchange was detected." + ) + + def __init__( + self, + transaction: Transaction, + *, + suppress_force_validation: bool = False, + ) -> None: + super().__init__() + self._suppress_force_required_validation = suppress_force_validation + self._transaction = transaction + + @classmethod + def has_unsafe_transfer_to_exchange(cls, result: ValidationResult) -> bool: + """Check if unsafe exchange transfer was detected in the result .""" + return ( + cls.HBD_TRANSFER_MSG_ERROR in result.failure_descriptions + or cls.MEMOLESS_HIVE_TRANSFER_MSG_ERROR in result.failure_descriptions + ) + + @classmethod + def has_unsafe_operation_to_exchange(cls, result: ValidationResult) -> bool: + """Check if unsafe exchange operations was detected in the result .""" + return cls.UNSAFE_EXCHANGE_OPERATION_MSG_ERROR in result.failure_descriptions + + def validate(self, value: str) -> ValidationResult: + """Validate the given value - exchange name.""" + validators = [ + Function(self._validate_hbd_transfer_operation, self.HBD_TRANSFER_MSG_ERROR), + Function(self._validate_memoless_transfer_operation, self.MEMOLESS_HIVE_TRANSFER_MSG_ERROR), + ] + if not self._suppress_force_required_validation: + validators.append( + Function( + self._validate_unsafe_exchange_operation, + self.UNSAFE_EXCHANGE_OPERATION_MSG_ERROR, + ) + ) + return ValidationResult.merge([validator.validate(value) for validator in validators]) + + def _validate_hbd_transfer_operation(self, value: str) -> bool: + """Validate if the transaction has a HBD transfer operations.""" + visitor = PotentialExchangeOperationsAccountCollector() + self._transaction.accept(visitor) + return not visitor.has_hbd_transfer_operations_to_exchange(value) + + def _validate_memoless_transfer_operation(self, value: str) -> bool: + """Validate if the transaction has a memoless transfer operations.""" + visitor = PotentialExchangeOperationsAccountCollector() + self._transaction.accept(visitor) + return not visitor.has_memoless_transfer_operations_to_exchange(value) + + def _validate_unsafe_exchange_operation(self, value: str) -> bool: + """Validate if the transaction has unsafe exchange operations.""" + visitor = PotentialExchangeOperationsAccountCollector() + self._transaction.accept(visitor) + return not visitor.has_unsafe_operation_to_exchange(value) + + +class ExchangeOperationsValidatorCli(ExchangeOperationsValidator): + """CLI-specific validator for exchange operations.""" + + UNSAFE_EXCHANGE_OPERATION_MSG_ERROR: ClassVar[str] = ( + f"{ExchangeOperationsValidator.UNSAFE_EXCHANGE_OPERATION_MSG_ERROR}" + " You can force the process by using the `--force` flag." + ) -- GitLab From 30b65b8fa67a1f17f78f3a28577d93ff70b8d32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Wed, 30 Apr 2025 07:37:33 +0000 Subject: [PATCH 05/17] Add force cli flag option. --- clive/__private/cli/common/parameters/options.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clive/__private/cli/common/parameters/options.py b/clive/__private/cli/common/parameters/options.py index 66433a80d4..b9f26440b4 100644 --- a/clive/__private/cli/common/parameters/options.py +++ b/clive/__private/cli/common/parameters/options.py @@ -109,6 +109,16 @@ page_no = typer.Option( help="Page number to display, considering the given page size.", ) +force_value = typer.Option( + default=False, + help=( + "This flag is required when performing operations to exchange accounts.\n" + "Some operations are not handled by exchanges.\n" + "Use --force to explicitly confirm and proceed with the operation despite this limitation." + ), + show_default=False, +) + # OPERATION COMMON OPTIONS >> _operation_common_option = partial(modified_param, rich_help_panel=OPERATION_COMMON_OPTIONS_PANEL_TITLE) -- GitLab From 7e30cea1bbc04ecd0c09e65b183b4ec367bb063f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Wed, 30 Apr 2025 09:33:37 +0000 Subject: [PATCH 06/17] Introduce ForceableCLICommand class. This class holds force flag, that will be used for validators to check, is command is forceable. --- .../cli/commands/abc/forceable_cli_command.py | 11 ++++++++++ .../perform_actions_on_transaction_command.py | 3 ++- .../cli/commands/configure/profile.py | 4 ++-- .../commands/process/process_delegations.py | 8 +++---- .../commands/process/process_transaction.py | 19 ++++++++-------- .../process/process_transfer_schedule.py | 22 +++++++++---------- .../process/process_withdraw_routes.py | 8 +++---- .../cli/process/hive_power/delegations.py | 2 ++ .../cli/process/hive_power/power_up.py | 2 ++ .../cli/process/hive_power/withdraw_routes.py | 2 ++ clive/__private/cli/process/main.py | 2 ++ clive/__private/cli/process/savings.py | 4 ++++ .../cli/process/transfer_schedule.py | 4 ++++ 13 files changed, 59 insertions(+), 32 deletions(-) create mode 100644 clive/__private/cli/commands/abc/forceable_cli_command.py diff --git a/clive/__private/cli/commands/abc/forceable_cli_command.py b/clive/__private/cli/commands/abc/forceable_cli_command.py new file mode 100644 index 0000000000..77208c531e --- /dev/null +++ b/clive/__private/cli/commands/abc/forceable_cli_command.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + + +@dataclass(kw_only=True) +class ForceableCLICommand: + """A base class for CLI commands that can be forced to execute.""" + + force: bool = field(default=False) + """Whether to force the execution of the command.""" diff --git a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py index 8687775892..a26a55d539 100644 --- a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py +++ b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING import rich import typer +from clive.__private.cli.commands.abc.forceable_cli_command import ForceableCLICommand from clive.__private.cli.commands.abc.world_based_command import WorldBasedCommand from clive.__private.cli.exceptions import ( CLIBroadcastCannotBeUsedWithForceUnsignError, @@ -30,7 +31,7 @@ if TYPE_CHECKING: @dataclass(kw_only=True) -class PerformActionsOnTransactionCommand(WorldBasedCommand, ABC): +class PerformActionsOnTransactionCommand(WorldBasedCommand, ForceableCLICommand, ABC): sign: str | None = None already_signed_mode: AlreadySignedMode = ALREADY_SIGNED_MODE_DEFAULT force_unsign: bool = False diff --git a/clive/__private/cli/commands/configure/profile.py b/clive/__private/cli/commands/configure/profile.py index fd764e993c..e9456afe50 100644 --- a/clive/__private/cli/commands/configure/profile.py +++ b/clive/__private/cli/commands/configure/profile.py @@ -8,6 +8,7 @@ from getpass import getpass from beekeepy.exceptions import CommunicationError from clive.__private.cli.commands.abc.external_cli_command import ExternalCLICommand +from clive.__private.cli.commands.abc.forceable_cli_command import ForceableCLICommand from clive.__private.cli.commands.abc.world_based_command import WorldBasedCommand from clive.__private.cli.exceptions import ( CLICreatingProfileCommunicationError, @@ -97,9 +98,8 @@ class CreateProfile(WorldBasedCommand): @dataclass(kw_only=True) -class DeleteProfile(ExternalCLICommand): +class DeleteProfile(ExternalCLICommand, ForceableCLICommand): profile_name: str - force: bool async def _run(self) -> None: try: diff --git a/clive/__private/cli/commands/process/process_delegations.py b/clive/__private/cli/commands/process/process_delegations.py index 47019dabe5..d3c53e1668 100644 --- a/clive/__private/cli/commands/process/process_delegations.py +++ b/clive/__private/cli/commands/process/process_delegations.py @@ -19,6 +19,10 @@ class ProcessDelegations(OperationCommand): delegatee: str amount: Asset.VotingT + async def validate(self) -> None: + await self._validate_amount() + await super().validate() + async def _create_operation(self) -> DelegateVestingSharesOperation: vesting_shares = await ensure_vests_async(self.amount, self.world) @@ -28,10 +32,6 @@ class ProcessDelegations(OperationCommand): vesting_shares=vesting_shares, ) - async def validate(self) -> None: - await self._validate_amount() - await super().validate() - async def _validate_amount(self) -> None: if self.amount in DELEGATION_REMOVE_ASSETS: raise DelegationsZeroAmountError diff --git a/clive/__private/cli/commands/process/process_transaction.py b/clive/__private/cli/commands/process/process_transaction.py index 8e9c8b50a0..0a69686c06 100644 --- a/clive/__private/cli/commands/process/process_transaction.py +++ b/clive/__private/cli/commands/process/process_transaction.py @@ -18,7 +18,6 @@ if TYPE_CHECKING: @dataclass(kw_only=True) class ProcessTransaction(PerformActionsOnTransactionCommand): from_file: str | Path - _loaded_transaction: Transaction | None = None @property @@ -31,15 +30,6 @@ class ProcessTransaction(PerformActionsOnTransactionCommand): self._loaded_transaction = await self.__load_transaction() return self._loaded_transaction - async def __load_transaction(self) -> Transaction: - return await LoadTransaction(file_path=self.from_file_path).execute_with_result() - - async def _get_transaction_content(self) -> Transaction: - return await self.__loaded_transaction - - def _get_transaction_created_message(self) -> str: - return "loaded" - async def validate(self) -> None: """ Validate given options before taking any action. @@ -76,3 +66,12 @@ class ProcessTransaction(PerformActionsOnTransactionCommand): async def _is_transaction_signed(self) -> bool: return (await self.__loaded_transaction).is_signed + + async def _get_transaction_content(self) -> Transaction: + return await self.__loaded_transaction + + def _get_transaction_created_message(self) -> str: + return "loaded" + + async def __load_transaction(self) -> Transaction: + return await LoadTransaction(file_path=self.from_file_path).execute_with_result() diff --git a/clive/__private/cli/commands/process/process_transfer_schedule.py b/clive/__private/cli/commands/process/process_transfer_schedule.py index 7175a1f9e0..8d5466ac67 100644 --- a/clive/__private/cli/commands/process/process_transfer_schedule.py +++ b/clive/__private/cli/commands/process/process_transfer_schedule.py @@ -107,17 +107,6 @@ class _ProcessTransferScheduleCreateModifyCommon(_ProcessTransferScheduleCommon) assert self.frequency is not None, "Value of frequency is known at this point." return self.frequency - async def _create_operation(self) -> RecurrentTransferOperation: - return RecurrentTransferOperation( - from_=self.from_account, - to=self.to, - amount=self.amount, - memo=self.memo, - recurrence=timedelta_to_int_hours(self.frequency_ensure), - executions=self.repeat, - extensions=self._create_recurrent_transfer_pair_id_extension(), - ) - def validate_amount_not_a_removal_value(self) -> None: """ Validate amount for create, and modify calls. @@ -134,6 +123,17 @@ class _ProcessTransferScheduleCreateModifyCommon(_ProcessTransferScheduleCommon) if scheduled_transfer_lifetime > SCHEDULED_TRANSFER_MAX_LIFETIME: raise ProcessTransferScheduleTooLongLifetimeError(requested_lifetime=scheduled_transfer_lifetime) + async def _create_operation(self) -> RecurrentTransferOperation: + return RecurrentTransferOperation( + from_=self.from_account, + to=self.to, + amount=self.amount, + memo=self.memo, + recurrence=timedelta_to_int_hours(self.frequency_ensure), + executions=self.repeat, + extensions=self._create_recurrent_transfer_pair_id_extension(), + ) + @dataclass(kw_only=True) class ProcessTransferScheduleCreate(_ProcessTransferScheduleCreateModifyCommon): diff --git a/clive/__private/cli/commands/process/process_withdraw_routes.py b/clive/__private/cli/commands/process/process_withdraw_routes.py index 517b06ff13..6fdf661f7f 100644 --- a/clive/__private/cli/commands/process/process_withdraw_routes.py +++ b/clive/__private/cli/commands/process/process_withdraw_routes.py @@ -17,6 +17,10 @@ class ProcessWithdrawRoutes(OperationCommand): percent: Decimal auto_vest: bool + async def validate(self) -> None: + await self._validate_percent() + await super().validate() + async def _create_operation(self) -> SetWithdrawVestingRouteOperation: return SetWithdrawVestingRouteOperation( from_account=self.from_account, @@ -25,10 +29,6 @@ class ProcessWithdrawRoutes(OperationCommand): auto_vest=self.auto_vest, ) - async def validate(self) -> None: - await self._validate_percent() - await super().validate() - async def _validate_percent(self) -> None: if self.percent == PERCENT_TO_REMOVE_WITHDRAW_ROUTE: raise WithdrawRoutesZeroPercentError diff --git a/clive/__private/cli/process/hive_power/delegations.py b/clive/__private/cli/process/hive_power/delegations.py index 32511c28b1..714e7a6fd4 100644 --- a/clive/__private/cli/process/hive_power/delegations.py +++ b/clive/__private/cli/process/hive_power/delegations.py @@ -28,6 +28,7 @@ async def process_delegations_set( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Add or modify vesting shares delegation for pair of accounts "account-name" and "delegatee".""" from clive.__private.cli.commands.process.process_delegations import ProcessDelegations @@ -40,6 +41,7 @@ async def process_delegations_set( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ) await operation.run() diff --git a/clive/__private/cli/process/hive_power/power_up.py b/clive/__private/cli/process/hive_power/power_up.py index 3dd7623ab9..79bf6c4ff3 100644 --- a/clive/__private/cli/process/hive_power/power_up.py +++ b/clive/__private/cli/process/hive_power/power_up.py @@ -24,6 +24,7 @@ async def process_power_up( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Perform power-up by sending transfer_to_vesting_operation.""" from clive.__private.cli.commands.process.process_power_up import ProcessPowerUp @@ -36,4 +37,5 @@ async def process_power_up( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() diff --git a/clive/__private/cli/process/hive_power/withdraw_routes.py b/clive/__private/cli/process/hive_power/withdraw_routes.py index 250d9788d9..fcc2c513da 100644 --- a/clive/__private/cli/process/hive_power/withdraw_routes.py +++ b/clive/__private/cli/process/hive_power/withdraw_routes.py @@ -23,6 +23,7 @@ async def process_withdraw_routes_set( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Add new withdraw route/modify existing route for pair of accounts "from" and "to".""" from clive.__private.cli.commands.process.process_withdraw_routes import ProcessWithdrawRoutes @@ -35,6 +36,7 @@ async def process_withdraw_routes_set( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ) await operation.run() diff --git a/clive/__private/cli/process/main.py b/clive/__private/cli/process/main.py index d05fa577c8..958ded1d07 100644 --- a/clive/__private/cli/process/main.py +++ b/clive/__private/cli/process/main.py @@ -90,6 +90,7 @@ async def process_transaction( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Process a transaction from file.""" from clive.__private.cli.commands.process.process_transaction import ProcessTransaction @@ -104,6 +105,7 @@ async def process_transaction( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() diff --git a/clive/__private/cli/process/savings.py b/clive/__private/cli/process/savings.py index 8b410bbd36..91661a6539 100644 --- a/clive/__private/cli/process/savings.py +++ b/clive/__private/cli/process/savings.py @@ -22,6 +22,7 @@ async def process_deposit( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Immediately deposit funds to savings account.""" from clive.__private.cli.commands.process.process_deposit import ProcessDeposit @@ -36,6 +37,7 @@ async def process_deposit( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() @@ -53,6 +55,7 @@ async def process_withdrawal( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Initiate withdrawal of funds from savings account, it takes 3 days to complete.""" from clive.__private.cli.commands.process.process_withdrawal import ProcessWithdrawal @@ -67,6 +70,7 @@ async def process_withdrawal( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() diff --git a/clive/__private/cli/process/transfer_schedule.py b/clive/__private/cli/process/transfer_schedule.py index c6dcbf63ec..6134411fbc 100644 --- a/clive/__private/cli/process/transfer_schedule.py +++ b/clive/__private/cli/process/transfer_schedule.py @@ -66,6 +66,7 @@ async def process_transfer_schedule_create( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """Create a new recurrent transfer. First recurrent transfer will be sent immediately.""" from clive.__private.cli.commands.process.process_transfer_schedule import ProcessTransferScheduleCreate @@ -81,6 +82,7 @@ async def process_transfer_schedule_create( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() @@ -96,6 +98,7 @@ async def process_transfer_schedule_modify( # noqa: PLR0913 sign: str | None = options.sign, broadcast: bool = options.broadcast, # noqa: FBT001 save_file: str | None = options.save_file, + force: bool = options.force_value, # noqa: FBT001 ) -> None: """ Modify an existing recurrent transfer. @@ -115,6 +118,7 @@ async def process_transfer_schedule_modify( # noqa: PLR0913 sign=sign, broadcast=broadcast, save_file=save_file, + force=force, ).run() -- GitLab From 73282946dbfe2ff0d174dc37202b5e19336f3354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Wed, 30 Apr 2025 09:41:17 +0000 Subject: [PATCH 07/17] Add new validator to PerformActionsOnTransactionCommand: * _validate_operations_to_exchange - that will validate operations to exchange --- .../abc/perform_actions_on_transaction_command.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py index a26a55d539..dd19c0f5ac 100644 --- a/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py +++ b/clive/__private/cli/commands/abc/perform_actions_on_transaction_command.py @@ -16,12 +16,14 @@ from clive.__private.cli.exceptions import ( CLIPrettyError, CLITransactionBadAccountError, CLITransactionNotSignedError, + CLITransactionToExchangeError, CLITransactionUnknownAccountError, ) from clive.__private.core.commands.sign import ALREADY_SIGNED_MODE_DEFAULT, AlreadySignedMode from clive.__private.core.ensure_transaction import ensure_transaction from clive.__private.core.formatters.humanize import humanize_validation_result from clive.__private.core.keys.key_manager import KeyNotFoundError +from clive.__private.validators.exchange_operations_validator import ExchangeOperationsValidatorCli from clive.__private.validators.path_validator import PathValidator if TYPE_CHECKING: @@ -57,6 +59,7 @@ class PerformActionsOnTransactionCommand(WorldBasedCommand, ForceableCLICommand, await self._validate_bad_accounts() if self.profile.should_enable_known_accounts: await self._validate_unknown_accounts() + await self._validate_operations_to_exchange() await super().validate_inside_context_manager() async def _run(self) -> None: @@ -119,6 +122,17 @@ class PerformActionsOnTransactionCommand(WorldBasedCommand, ForceableCLICommand, if bad_accounts: raise CLITransactionBadAccountError(*bad_accounts) + async def _validate_operations_to_exchange(self) -> None: + transaction_ensured = await self.get_transaction() + exchange_operation_validator = ExchangeOperationsValidatorCli( + transaction=transaction_ensured, + suppress_force_validation=self.force, + ) + for exchange in self.world.known_exchanges: + result = exchange_operation_validator.validate(exchange.name) + if not result.is_valid: + raise CLITransactionToExchangeError(humanize_validation_result(result)) + def _get_transaction_created_message(self) -> str: return "created" -- GitLab From 5e946048b6a155a5c8aa635f379aad3c5436b99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Wed, 30 Apr 2025 11:29:25 +0000 Subject: [PATCH 08/17] TUI: Add validation for memoless transfer to exchange while loading transaction file and notify about any forceable operation, --- clive/__private/core/formatters/humanize.py | 4 +- .../transaction_summary.py | 41 ++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/clive/__private/core/formatters/humanize.py b/clive/__private/core/formatters/humanize.py index 3570cf88d2..bf761aa015 100644 --- a/clive/__private/core/formatters/humanize.py +++ b/clive/__private/core/formatters/humanize.py @@ -85,14 +85,12 @@ def align_to_dot(*strings: str, center_to: int | str | None = None) -> list[str] return aligned_strings -def humanize_validation_result(result: ValidationResult, *, as_markup: bool = False) -> str: +def humanize_validation_result(result: ValidationResult) -> str: """Return failure description from ValidationResult if any exists, otherwise returns 'No failures' message.""" if result.is_valid: return "No failures" if len(result.failure_descriptions) > 1: - if as_markup: - return "\n".join(f"{desc}" for desc in result.failure_descriptions) return str(result.failure_descriptions) return result.failure_descriptions[0] diff --git a/clive/__private/ui/screens/transaction_summary/transaction_summary.py b/clive/__private/ui/screens/transaction_summary/transaction_summary.py index a82a99a815..a57a1f1e61 100644 --- a/clive/__private/ui/screens/transaction_summary/transaction_summary.py +++ b/clive/__private/ui/screens/transaction_summary/transaction_summary.py @@ -18,10 +18,12 @@ from clive.__private.core.constants.tui.messages import ( BAD_ACCOUNT_IN_LOADED_TRANSACTION_MESSAGE, ERROR_BAD_ACCOUNT_IN_LOADED_TRANSACTION_MESSAGE, ) +from clive.__private.core.formatters.humanize import humanize_validation_result from clive.__private.core.keys import PublicKey from clive.__private.core.keys.key_manager import KeyNotFoundError from clive.__private.ui.clive_widget import CliveWidget from clive.__private.ui.dialogs import ConfirmInvalidateSignaturesDialog +from clive.__private.ui.dialogs.confirm_action_dialog_with_known_exchange import ConfirmActionDialogWithKnownExchange from clive.__private.ui.get_css import get_relative_css_path from clive.__private.ui.screens.base_screen import BaseScreen from clive.__private.ui.screens.transaction_summary.cart_table import CartTable @@ -38,6 +40,7 @@ from clive.__private.ui.widgets.select_file_to_save_transaction import ( SaveTransactionResult, SelectFileToSaveTransaction, ) +from clive.__private.validators.exchange_operations_validator import ExchangeOperationsValidator from clive.exceptions import NoItemSelectedError if TYPE_CHECKING: @@ -198,7 +201,14 @@ class TransactionSummary(BaseScreen): if self.profile.transaction else None ) - self.app.push_screen(SelectFile(notice=notify_text), self._load_transaction_from_file) + + # It is required to call push_screen_wait from worker. + # Otherwise there was `push_screen must be run from a worker when `wait_for_dismiss` is True ` error observed. + # See: https://textual.textualize.io/guide/screens/#waiting-for-screens + def delegate_work(result: SaveFileResult | None) -> None: + self.run_worker(self._load_transaction_from_file(result)) + + self.app.push_screen(SelectFile(notice=notify_text), delegate_work) @on(ButtonBroadcast.Pressed) async def action_broadcast(self) -> None: @@ -295,6 +305,9 @@ class TransactionSummary(BaseScreen): if self._check_for_unknown_bad_accounts(loaded_transaction): return + if not (await self._validate_operations_to_exchange(loaded_transaction)): + return + if not loaded_transaction.is_tapos_set: self.notify("TaPoS metadata was not set, updating automatically...") await self._update_transaction_metadata() @@ -391,3 +404,29 @@ class TransactionSummary(BaseScreen): self.notify(BAD_ACCOUNT_IN_LOADED_TRANSACTION_MESSAGE, severity="warning") return False + + async def _validate_operations_to_exchange(self, loaded_transaction: Transaction) -> bool: + """Validate operations from transaction to the exchange.""" + operation_validator = ExchangeOperationsValidator(transaction=loaded_transaction) + + is_confirmation_required = False + for exchange in self.world.known_exchanges: + result = operation_validator.validate(exchange.name) + if result.is_valid: + continue + + if ExchangeOperationsValidator.has_unsafe_transfer_to_exchange(result): + self.notify( + f"Cannot load transaction.\n{humanize_validation_result(result)}", + severity="error", + markup=False, + ) + return False + + if ExchangeOperationsValidator.has_unsafe_operation_to_exchange(result): + is_confirmation_required = True + + if is_confirmation_required: + return await self.app.push_screen_wait(ConfirmActionDialogWithKnownExchange()) + + return True -- GitLab From dccda512ce6d463b713710a207c08cca8d2557d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Thu, 8 May 2025 21:44:39 +0000 Subject: [PATCH 09/17] Add test checking for known exchanges in cli. --- .../clive_local_tools/cli/cli_tester.py | 8 + .../clive_local_tools/helpers.py | 11 ++ .../testnet_block_log/__init__.py | 2 + .../testnet_block_log/constants.py | 4 + .../test_bad_accounts_validation.py | 2 +- .../test_known_accounts_enabled.py | 2 +- .../cli/exchange_operations/__init__.py | 0 .../test_force_required_operations.py | 167 +++++++++++++++++ .../cli/exchange_operations/test_transfer.py | 174 ++++++++++++++++++ 9 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 tests/functional/cli/exchange_operations/__init__.py create mode 100644 tests/functional/cli/exchange_operations/test_force_required_operations.py create mode 100644 tests/functional/cli/exchange_operations/test_transfer.py diff --git a/tests/clive-local-tools/clive_local_tools/cli/cli_tester.py b/tests/clive-local-tools/clive_local_tools/cli/cli_tester.py index e70f41d334..b41fd5466b 100644 --- a/tests/clive-local-tools/clive_local_tools/cli/cli_tester.py +++ b/tests/clive-local-tools/clive_local_tools/cli/cli_tester.py @@ -174,6 +174,7 @@ class CLITester: amount: tt.Asset.AnyT, memo: str | None = None, from_: str | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options( ["process", "savings", "deposit"], @@ -191,6 +192,7 @@ class CLITester: amount: tt.Asset.AnyT, memo: str | None = None, from_: str | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options( ["process", "savings", "withdrawal"], @@ -236,6 +238,7 @@ class CLITester: sign: str | None = None, broadcast: bool | None = None, save_file: Path | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options( ["process", "transaction"], @@ -265,6 +268,7 @@ class CLITester: sign: str | None = None, broadcast: bool | None = None, save_file: Path | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options(["process", "power-up"], **extract_params(locals())) @@ -308,6 +312,7 @@ class CLITester: sign: str | None = None, broadcast: bool | None = None, save_file: Path | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options(["process", "delegations", "set"], **extract_params(locals())) @@ -332,6 +337,7 @@ class CLITester: sign: str | None = None, broadcast: bool | None = None, save_file: Path | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options(["process", "withdraw-routes", "set"], **extract_params(locals())) @@ -484,6 +490,7 @@ class CLITester: broadcast: bool | None = None, save_file: Path | None = None, memo: str | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options( ["process", "transfer-schedule", "create"], **extract_params(locals()) @@ -502,6 +509,7 @@ class CLITester: broadcast: bool | None = None, save_file: Path | None = None, memo: str | None = None, + force: bool | None = None, ) -> Result: return self.__invoke_command_with_options( ["process", "transfer-schedule", "modify"], **extract_params(locals()) diff --git a/tests/clive-local-tools/clive_local_tools/helpers.py b/tests/clive-local-tools/clive_local_tools/helpers.py index 9c404a9400..81d6feef51 100644 --- a/tests/clive-local-tools/clive_local_tools/helpers.py +++ b/tests/clive-local-tools/clive_local_tools/helpers.py @@ -7,10 +7,13 @@ from rich.panel import Panel from typer import rich_utils from clive.__private.core.constants.terminal import TERMINAL_HEIGHT, TERMINAL_WIDTH +from clive.__private.core.ensure_transaction import TransactionConvertibleType, ensure_transaction from clive.__private.core.validate_schema_field import validate_schema_field from clive.__private.models.schemas import TransactionId if TYPE_CHECKING: + from pathlib import Path + from click import ClickException @@ -40,3 +43,11 @@ def get_formatted_error_message(error: ClickException) -> str: with console.capture() as capture: console.print(panel) return capture.get() + + +def create_transaction_file(path: Path, operations: TransactionConvertibleType) -> Path: + transaction_path = path / "trx.json" + transaction = ensure_transaction(operations) + transaction_serialized = transaction.json(by_alias=True) + transaction_path.write_text(transaction_serialized) + return transaction_path diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/__init__.py b/tests/clive-local-tools/clive_local_tools/testnet_block_log/__init__.py index f0aaf7408d..fe8d4ce502 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/__init__.py +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/__init__.py @@ -8,6 +8,7 @@ from .constants import ( CREATOR_ACCOUNT, EMPTY_ACCOUNT, KNOWN_ACCOUNTS, + KNOWN_EXCHANGES_NAMES, PROPOSALS, UNKNOWN_ACCOUNT, WATCHED_ACCOUNTS_DATA, @@ -26,6 +27,7 @@ __all__ = [ "CREATOR_ACCOUNT", "EMPTY_ACCOUNT", "KNOWN_ACCOUNTS", + "KNOWN_EXCHANGES_NAMES", "PROPOSALS", "UNKNOWN_ACCOUNT", "WATCHED_ACCOUNTS_DATA", diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/constants.py b/tests/clive-local-tools/clive_local_tools/testnet_block_log/constants.py index 09564ab7b1..43c5594094 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/constants.py +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/constants.py @@ -5,6 +5,7 @@ from typing import Final import test_tools as tt +from clive.__private.core.known_exchanges import KnownExchanges from clive_local_tools.data.generates import generate_proposal_name, generate_witness_name from clive_local_tools.data.models import AccountData @@ -51,10 +52,13 @@ ALT_WORKING_ACCOUNT2_DATA: Final[AccountData] = AccountData( UNKNOWN_ACCOUNT: Final[str] = "null" +KNOWN_EXCHANGES_NAMES: Final[list[str]] = [exchange.name for exchange in KnownExchanges()] + KNOWN_ACCOUNTS: Final[list[str]] = [ EMPTY_ACCOUNT.name, *WATCHED_ACCOUNTS_NAMES, WORKING_ACCOUNT_NAME, ALT_WORKING_ACCOUNT1_NAME, ALT_WORKING_ACCOUNT2_NAME, + *KNOWN_EXCHANGES_NAMES, ] diff --git a/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py b/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py index 292368b2e5..03fcadfca6 100644 --- a/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py +++ b/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py @@ -244,7 +244,7 @@ async def test_no_validation_of_canceling_savings_withdrawal_to_account_that_bec ) -> None: """It should be possible to cancel savings withdrawal, even if account is on bad account list.""" # ARRANGE - request_id: int = 0 + request_id: int = 22 cli_tester.process_savings_withdrawal( to=TEMPORARY_BAD_ACCOUNT, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, request_id=request_id ) diff --git a/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py b/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py index dca7542aaf..1c4238b9d6 100644 --- a/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py +++ b/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py @@ -165,7 +165,7 @@ async def test_no_validation_of_canceling_savings_withdrawal_to_account_that_was ) -> None: """It should be possible to cancel savings withdrawal, even if account is not on known account list.""" # ARRANGE - request_id: int = 0 + request_id: int = 23 cli_tester.process_savings_withdrawal( to=KNOWN_ACCOUNT, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, request_id=request_id ) diff --git a/tests/functional/cli/exchange_operations/__init__.py b/tests/functional/cli/exchange_operations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/cli/exchange_operations/test_force_required_operations.py b/tests/functional/cli/exchange_operations/test_force_required_operations.py new file mode 100644 index 0000000000..9672a1746c --- /dev/null +++ b/tests/functional/cli/exchange_operations/test_force_required_operations.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Final + +import pytest +import test_tools as tt + +from clive.__private.cli.exceptions import CLITransactionToExchangeError +from clive.__private.models.schemas import TransferFromSavingsOperation, TransferToSavingsOperation +from clive.__private.validators.exchange_operations_validator import ExchangeOperationsValidatorCli +from clive_local_tools.cli.checkers import assert_output_contains +from clive_local_tools.cli.exceptions import CLITestCommandError +from clive_local_tools.data.constants import WORKING_ACCOUNT_KEY_ALIAS +from clive_local_tools.helpers import create_transaction_file, get_formatted_error_message +from clive_local_tools.testnet_block_log import WORKING_ACCOUNT_NAME +from clive_local_tools.testnet_block_log.constants import KNOWN_EXCHANGES_NAMES + +if TYPE_CHECKING: + from collections.abc import Callable + from pathlib import Path + + from clive_local_tools.cli.cli_tester import CLITester + + +AMOUNT: Final[tt.Asset.HiveT] = tt.Asset.Hive(1) +KNOWN_EXCHANGE_NAME: Final[str] = KNOWN_EXCHANGES_NAMES[0] + + +@pytest.fixture +def transaction_with_forceable_operation_path(tmp_path: Path) -> Path: + operations = [ + TransferToSavingsOperation( + from_=WORKING_ACCOUNT_NAME, + to=KNOWN_EXCHANGE_NAME, + amount=tt.Asset.Hive(1), + memo="transfer_to_savings_operation forceable test", + ), + TransferFromSavingsOperation( + from_=WORKING_ACCOUNT_NAME, + to=KNOWN_EXCHANGE_NAME, + amount=tt.Asset.Hive(1), + memo="transfer_from_savings_operation forceable test", + request_id=1234, + ), + ] + return create_transaction_file(tmp_path, operations) + + +def _assert_operation_to_exchange(send_operation_cb: Callable[[], None], *, force: bool) -> None: + if force: + send_operation_cb() + else: + expected_error_msg = get_formatted_error_message( + CLITransactionToExchangeError(ExchangeOperationsValidatorCli.UNSAFE_EXCHANGE_OPERATION_MSG_ERROR) + ) + with pytest.raises(CLITestCommandError) as error: + send_operation_cb() + assert_output_contains(expected_error_msg, error.value.stdout) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_loading_transaction( + cli_tester: CLITester, transaction_with_forceable_operation_path: Path, *, force: bool +) -> None: + # ARRANGE + def send_operation() -> None: + cli_tester.process_transaction( + from_file=transaction_with_forceable_operation_path, + sign=WORKING_ACCOUNT_KEY_ALIAS, + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validate_of_performing_recurrent_transfer_to_exchange(cli_tester: CLITester, *, force: bool) -> None: + # ARRANGE + def send_operation() -> None: + cli_tester.process_transfer_schedule_create( + to=KNOWN_EXCHANGE_NAME, + amount=AMOUNT, + sign=WORKING_ACCOUNT_KEY_ALIAS, + repeat=2, + frequency="24h", + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validate_of_performing_withdrawing_to_exchange(cli_tester: CLITester, *, force: bool) -> None: + # ARRANGE + def send_operation() -> None: + request_id: int = 4123 + cli_tester.process_savings_withdrawal( + to=KNOWN_EXCHANGE_NAME, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, force=force, request_id=request_id + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validate_of_performing_deposit_to_exchange(cli_tester: CLITester, *, force: bool) -> None: + # ARRANGE + def send_operation() -> None: + cli_tester.process_savings_deposit( + to=KNOWN_EXCHANGE_NAME, + amount=AMOUNT, + sign=WORKING_ACCOUNT_KEY_ALIAS, + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validation_of_powering_up_to_exchange(cli_tester: CLITester, *, force: bool) -> None: + # ARRANGE + def send_operation() -> None: + cli_tester.process_power_up( + amount=AMOUNT, + to=KNOWN_EXCHANGE_NAME, + sign=WORKING_ACCOUNT_KEY_ALIAS, + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validate_of_performing_delegation_set_to_exchange(cli_tester: CLITester, *, force: bool) -> None: + # ARRANGE + def send_operation() -> None: + cli_tester.process_delegations_set( + delegatee=KNOWN_EXCHANGE_NAME, + amount=AMOUNT, + sign=WORKING_ACCOUNT_KEY_ALIAS, + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) + + +@pytest.mark.parametrize("force", [True, False]) +async def test_validate_of_performing_of_withdrawal_routes_set_to_exchange( + cli_tester: CLITester, *, force: bool +) -> None: + # ARRANGE + def send_operation() -> None: + percent: int = 30 + cli_tester.process_withdraw_routes_set( + to=KNOWN_EXCHANGE_NAME, + percent=percent, + sign=WORKING_ACCOUNT_KEY_ALIAS, + force=force, + ) + + # ACT & ASSERT + _assert_operation_to_exchange(send_operation, force=force) diff --git a/tests/functional/cli/exchange_operations/test_transfer.py b/tests/functional/cli/exchange_operations/test_transfer.py new file mode 100644 index 0000000000..b1e49c4917 --- /dev/null +++ b/tests/functional/cli/exchange_operations/test_transfer.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Final + +import pytest +import test_tools as tt + +from clive.__private.cli.exceptions import CLITransactionToExchangeError +from clive.__private.models.schemas import TransferOperation +from clive.__private.validators.exchange_operations_validator import ( + ExchangeOperationsValidatorCli, +) +from clive_local_tools.cli.exceptions import CLITestCommandError +from clive_local_tools.data.constants import WORKING_ACCOUNT_KEY_ALIAS +from clive_local_tools.helpers import create_transaction_file, get_formatted_error_message +from clive_local_tools.testnet_block_log import WORKING_ACCOUNT_NAME +from clive_local_tools.testnet_block_log.constants import KNOWN_EXCHANGES_NAMES + +if TYPE_CHECKING: + from collections.abc import Callable + from pathlib import Path + + from clive_local_tools.cli.cli_tester import CLITester + +KNOWN_EXCHANGE_NAME: Final[str] = KNOWN_EXCHANGES_NAMES[0] +ACCOUNT_DOES_NOT_EXIST_ERROR_MSG: Final[str] = f"Account {KNOWN_EXCHANGE_NAME} doesn't exist" +MEMOLESS_TRANSFER_MSG_ERROR: Final[str] = get_formatted_error_message( + CLITransactionToExchangeError(ExchangeOperationsValidatorCli.MEMOLESS_HIVE_TRANSFER_MSG_ERROR) +) + +HBD_TRANSFER_OPERATION_MSG_ERROR: Final[str] = get_formatted_error_message( + CLITransactionToExchangeError(ExchangeOperationsValidatorCli.HBD_TRANSFER_MSG_ERROR) +) + +FORCE_REQUIRED_OPERATION_MSG_ERROR: Final[str] = get_formatted_error_message( + CLITransactionToExchangeError(ExchangeOperationsValidatorCli.UNSAFE_EXCHANGE_OPERATION_MSG_ERROR) +) + +MEMO_MSG: Final[str] = "test memo to exchange" +EMPTY_MEMO_MSG: Final[str] = "" + + +def _assert_operation_error(operation_cb: Callable[[], None], expected_message: str) -> None: + with pytest.raises(CLITestCommandError, match=expected_message): + operation_cb() + + +@pytest.fixture +def transaction_with_memoless_transfer_path(tmp_path: Path) -> Path: + operations = [ + TransferOperation( + from_=WORKING_ACCOUNT_NAME, + to=KNOWN_EXCHANGE_NAME, + amount=tt.Asset.Hive(1000), + memo=EMPTY_MEMO_MSG, + ) + ] + return create_transaction_file(tmp_path, operations) + + +@pytest.fixture +def transaction_with_hbd_transfer_path(tmp_path: Path) -> Path: + operations = [ + TransferOperation( + from_=WORKING_ACCOUNT_NAME, + to=KNOWN_EXCHANGE_NAME, + amount=tt.Asset.Hbd(1000), + memo="HBD transfer to exchange", + ) + ] + return create_transaction_file(tmp_path, operations) + + +@pytest.mark.parametrize("amount", [tt.Asset.Hive(10), tt.Asset.Hbd(10)]) +async def test_validate_memoless_transfer_to_exchange( + cli_tester: CLITester, amount: tt.Asset.HiveT | tt.Asset.HbdT +) -> None: + """ + Verify that memoless transfer to exchange is not allowed. + + This test checks performing transfer that has no memo to exchange, + it will generate an error, and transfer will not be broadcasted. + """ + + # ARRANGE + def operation() -> None: + cli_tester.process_transfer( + to=KNOWN_EXCHANGE_NAME, + amount=amount, + sign=WORKING_ACCOUNT_KEY_ALIAS, + memo=EMPTY_MEMO_MSG, + ) + + # ACT & ASSERT + _assert_operation_error(operation, MEMOLESS_TRANSFER_MSG_ERROR) + + +async def test_validate_performing_transaction_with_memoless_transfer_to_exchange( + cli_tester: CLITester, + transaction_with_memoless_transfer_path: Path, +) -> None: + """ + Verify performing transaction memoless transfer to exchange. + + This test checks if transaction with memoless transfer to exchange is not allowed. + It will generate an error, and transaction will not be broadcasted. + """ + + # ARRANGE + def operation() -> None: + cli_tester.process_transaction( + from_file=transaction_with_memoless_transfer_path, + sign=WORKING_ACCOUNT_KEY_ALIAS, + ) + + # ACT & ASSERT + _assert_operation_error(operation, MEMOLESS_TRANSFER_MSG_ERROR) + + +async def test_validate_performing_transaction_with_hbd_transfer_to_exchange( + cli_tester: CLITester, + transaction_with_hbd_transfer_path: Path, +) -> None: + """ + Verify performing transaction hbd transfer to exchange. + + This test checks if transaction with hbd transfer to exchange is not allowed. + It will generate an error, and transaction will not be broadcasted. + """ + + # ARRANGE + def operation() -> None: + cli_tester.process_transaction( + from_file=transaction_with_hbd_transfer_path, + sign=WORKING_ACCOUNT_KEY_ALIAS, + ) + + # ACT & ASSERT + _assert_operation_error(operation, HBD_TRANSFER_OPERATION_MSG_ERROR) + + +async def test_validate_hive_transfer_with_memo_to_exchange( + cli_tester: CLITester, +) -> None: + """HIVE transfers with memo are allowed to exchanges.""" + # ARRANGE + amount = tt.Asset.Hive(10.000) + + # ACT & ASSERT + cli_tester.process_transfer( + to=KNOWN_EXCHANGE_NAME, + amount=amount, + sign=WORKING_ACCOUNT_KEY_ALIAS, + memo=MEMO_MSG, + ) + + +async def test_validate_hbd_transfer_with_memo_to_exchange( + cli_tester: CLITester, +) -> None: + """HBD transfer operations to exchanges are not allowed.""" + # ARRANGE + amount = tt.Asset.Hbd(10.000) + + def operation() -> None: + cli_tester.process_transfer( + to=KNOWN_EXCHANGE_NAME, + amount=amount, + sign=WORKING_ACCOUNT_KEY_ALIAS, + memo=MEMO_MSG, + ) + + # ACT & ASSERT + _assert_operation_error(operation, HBD_TRANSFER_OPERATION_MSG_ERROR) -- GitLab From 85d0a7b5261c7555cee5b147278a29e948f7eb0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Mon, 2 Jun 2025 14:54:01 +0000 Subject: [PATCH 10/17] Add create_known_exchange_account to generate_block_log.py. It will allow to create known exchangs in blockchain. --- .../testnet_block_log/generate_block_log.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/generate_block_log.py b/tests/clive-local-tools/clive_local_tools/testnet_block_log/generate_block_log.py index 27f1c92461..048d53826d 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/generate_block_log.py +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/generate_block_log.py @@ -14,6 +14,7 @@ from clive_local_tools.testnet_block_log.constants import ( ALT_WORKING_ACCOUNT2_DATA, CREATOR_ACCOUNT, EMPTY_ACCOUNT, + KNOWN_EXCHANGES_NAMES, PROPOSALS, WATCHED_ACCOUNTS_DATA, WITNESSES, @@ -181,6 +182,14 @@ def create_empty_account(wallet: tt.Wallet) -> None: wallet.create_account(EMPTY_ACCOUNT.name) +def create_known_exchange_accounts(wallet: tt.Wallet) -> None: + tt.logger.info("Creating known exchange accounts...") + for exchange_name in KNOWN_EXCHANGES_NAMES: + wallet.create_account( + exchange_name, + ) + + def prepare_votes_for_witnesses(wallet: tt.Wallet) -> None: tt.logger.info("Prepare votes for witnesses...") with wallet.in_single_transaction(): @@ -213,6 +222,7 @@ def main() -> None: prepare_savings(wallet) prepare_votes_for_witnesses(wallet) create_empty_account(wallet) + create_known_exchange_accounts(wallet) tt.logger.info("Wait 21 blocks to schedule newly created witnesses into future state") node.wait_number_of_blocks(21) -- GitLab From f186968da3d61f07241542bf83df551e375d9ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Mon, 2 Jun 2025 18:41:01 +0000 Subject: [PATCH 11/17] Update blog log. --- .../blockchain/block_log_part.0001 | Bin 0 -> 25729 bytes .../testnet_block_log/config.ini | 52 ++++-------------- .../testnet_block_log/timestamp | 2 +- 3 files changed, 11 insertions(+), 43 deletions(-) create mode 100644 tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log_part.0001 diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log_part.0001 b/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log_part.0001 new file mode 100644 index 0000000000000000000000000000000000000000..043de920b7914ad063a767dfe879899fa52f6e01 GIT binary patch literal 25729 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 W*3+%-dZ24Fcw`+RO)Q=ih))3%ty}&8 literal 0 HcmV?d00001 diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini b/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini index 023c05a189..054e4597eb 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini @@ -1,43 +1,22 @@ -log-appender = {"appender":"stderr","stream":"std_error","time_format":"iso_8601_microseconds"} {"appender":"p2p","file":"logs/p2p/p2p.log","time_format":"iso_8601_milliseconds"} log-logger = {"name":"default","level":"debug","appender":"stderr"} {"name":"user","level":"debug","appender":"stderr"} {"name":"chainlock","level":"debug","appender":"p2p"} {"name":"sync","level":"debug","appender":"p2p"} {"name":"p2p","level":"debug","appender":"p2p"} -backtrace = yes +plugin = transaction_status_api +plugin = account_history_api plugin = witness -plugin = state_snapshot -plugin = account_by_key_api plugin = account_by_key -plugin = block_api -plugin = database_api -plugin = debug_node_api -plugin = network_node_api +plugin = rc_api plugin = wallet_bridge_api plugin = account_history_rocksdb -plugin = account_history_api plugin = reputation_api -plugin = rc_api -plugin = transaction_status_api -account-history-rocksdb-path = "blockchain/account-history-rocksdb-storage" -block-data-export-file = NONE -block-data-skip-empty = 0 -block-log-info-print-interval-seconds = 86400 -block-log-info-print-irreversible = 1 -block-log-info-print-file = ILOG -shared-file-dir = "blockchain" +plugin = account_by_key_api +plugin = network_node_api +plugin = block_api +plugin = state_snapshot +plugin = database_api +plugin = debug_node_api shared-file-size = 128M -shared-file-full-threshold = 0 -shared-file-scale-rate = 0 -market-history-bucket-size = [15,60,300,3600,86400] -market-history-buckets-per-size = 5760 p2p-endpoint = 0.0.0.0:0 -rc-stats-report-type = REGULAR -rc-stats-report-output = ILOG -block-log-split = -1 -snapshot-root-dir = "snapshot" -statsd-batchsize = 1 -transaction-status-block-depth = 64000 webserver-http-endpoint = 0.0.0.0:0 webserver-ws-endpoint = 0.0.0.0:0 -webserver-ws-deflate = 0 -webserver-thread-pool-size = 32 enable-stale-production = 1 required-participation = 0 witness = "initminer" @@ -161,15 +140,4 @@ private-key = 5JdQmXYK5ghJJQMEMhevkkBPB8jhjeJzVtEqxGWwo4SQStrXirY private-key = 5JDdGrA41aADWgMBXkvSc6n3V6MUcoFdxzVFrAgud2c75hGHuwu private-key = 5JQ2YZ642GfUmqx31F5nkj5R4GQZuDyjw8fEZ7bUCwEQ3gZW8Fz private-key = 5Jagcf1mdLp4455zuGziHsouu5qyBmE5LzmkiAqwhQuUSKYhvSJ -private-key = 5J8m4zbzqPvEytq9S3fyyUHLo9B8PDF8azbgTy5ZeyHKvVaMezS -enable-block-log-compression = 1 -enable-block-log-auto-fixing = 1 -block-log-compression-level = 15 -blockchain-thread-pool-size = 8 -block-stats-report-type = FULL -block-stats-report-output = ILOG -colony-threads = 4 -colony-start-at-block = 0 -colony-no-broadcast = 0 -pacemaker-min-offset = -300 -pacemaker-max-offset = 20000 \ No newline at end of file +private-key = 5J8m4zbzqPvEytq9S3fyyUHLo9B8PDF8azbgTy5ZeyHKvVaMezS \ No newline at end of file diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp b/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp index f2df189e51..832c1315b5 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp @@ -1 +1 @@ -@2024-08-07 13:03:39+00:00 \ No newline at end of file +@2025-06-03 14:31:33+00:00 \ No newline at end of file -- GitLab From 0bcde468aed79aed39d93355785e3e1795dc9c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Tue, 3 Jun 2025 14:43:41 +0200 Subject: [PATCH 12/17] Always use faketime when launching prepared testnet block log --- docker/Dockerfile | 10 ++++++++++ testnet_node.py | 2 -- .../testnet_block_log/prepared_data.py | 4 ++-- tests/tui/conftest.py | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a7400d0a09..b912e3d8aa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -128,6 +128,16 @@ COPY --chown=clive --from=hived_image "${HIVED_BINARIES_DIR_SOURCE}/hived" "${HI RUN strip -s "${HIVED_BINARIES_DIR_DEST}"/* +# Installing faketime lib, because it is needed in embedded testnet. +# The Faketime library is used in embedded testnet images to start blockchain nodes +# with the correct historical timestamp matching the generated block_log. +# Based on https://gitlab.syncad.com/hive/hive/blob/2a04c2be5d4b47c38ab498e588465bf041d294d6/scripts/setup_ubuntu.sh#L70 +RUN git clone --depth 1 --branch bw_timer_settime_fix https://gitlab.syncad.com/bwrona/faketime.git && \ + pushd faketime && CFLAGS="-O2 -DFAKE_STATELESS=1" make && \ + sudo make install && \ + popd && \ + rm -rf faketime + ARG CLIVE_SECRETS__DEFAULT_PRIVATE_KEY="5KTNAYSHVzhnVPrwHpKhc5QqNQt6aW8JsrMT7T4hyrKydzYvYik" ENV CLIVE_SECRETS__DEFAULT_PRIVATE_KEY=${CLIVE_SECRETS__DEFAULT_PRIVATE_KEY} diff --git a/testnet_node.py b/testnet_node.py index 319c71b57d..7dfea98caf 100644 --- a/testnet_node.py +++ b/testnet_node.py @@ -57,8 +57,6 @@ def init_argparse(args: Sequence[str]) -> argparse.Namespace: def prepare_node() -> tt.RawNode: - # TODO: time_offset/use_faketime option should be used there but faketime is not available in the embedded_testnet - # docker image yet return run_node(webserver_http_endpoint="0.0.0.0:8090") diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/prepared_data.py b/tests/clive-local-tools/clive_local_tools/testnet_block_log/prepared_data.py index 75afe6e22a..06b1bf7032 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/prepared_data.py +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/prepared_data.py @@ -29,11 +29,11 @@ def get_time_offset() -> str: return file.read() -def run_node(webserver_http_endpoint: str | None = None, *, use_faketime: bool = False) -> tt.RawNode: +def run_node(webserver_http_endpoint: str | None = None) -> tt.RawNode: config_lines = get_config().write_to_lines() block_log = get_block_log() alternate_chain_spec = tt.AlternateChainSpecs.parse_file(get_alternate_chain_spec_path()) - time_offset = get_time_offset() if use_faketime else None + time_offset = get_time_offset() node = tt.RawNode() node.config.load_from_lines(config_lines) diff --git a/tests/tui/conftest.py b/tests/tui/conftest.py index 71af1f7ed5..15202847e7 100644 --- a/tests/tui/conftest.py +++ b/tests/tui/conftest.py @@ -67,7 +67,7 @@ async def _prepare_profile_with_wallet_tui() -> None: @pytest.fixture def node_with_wallet() -> NodeWithWallet: - node = run_node(use_faketime=True) + node = run_node() wallet = tt.Wallet(attach_to=node) wallet.api.import_key(node.config.private_key[0]) -- GitLab From b378a0abc6a409fda68eb36c9acff845a0ba2963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Tue, 3 Jun 2025 14:45:46 +0200 Subject: [PATCH 13/17] Remove old block_log --- .../testnet_block_log/blockchain/block_log | Bin 24207 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log b/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log deleted file mode 100644 index e41f5d69812be776285d6bfa79e2174323621fb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24207 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 jc& Date: Tue, 3 Jun 2025 14:46:00 +0200 Subject: [PATCH 14/17] Regenerate block log --- .../alternate-chain-spec.json | 2 +- .../blockchain/block_log_part.0001 | Bin 25729 -> 25741 bytes .../testnet_block_log/config.ini | 18 +++++++++--------- .../testnet_block_log/timestamp | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/alternate-chain-spec.json b/tests/clive-local-tools/clive_local_tools/testnet_block_log/alternate-chain-spec.json index b47cdb72d6..9f6d6281a4 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/alternate-chain-spec.json +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/alternate-chain-spec.json @@ -1,5 +1,5 @@ { - "genesis_time": 1722949221, + "genesis_time": 1748956337, "hardfork_schedule": [ { "hardfork": 28, diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log_part.0001 b/tests/clive-local-tools/clive_local_tools/testnet_block_log/blockchain/block_log_part.0001 index 043de920b7914ad063a767dfe879899fa52f6e01..97845aba1e25a71cb93a562e0083e3be945d5c4d 100644 GIT binary patch delta 19520 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 Zw>fJ)-RiCfI-H6}))CU`;z@z{6#yEnJP-f? diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini b/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini index 054e4597eb..61f241011f 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/config.ini @@ -1,18 +1,18 @@ log-logger = {"name":"default","level":"debug","appender":"stderr"} {"name":"user","level":"debug","appender":"stderr"} {"name":"chainlock","level":"debug","appender":"p2p"} {"name":"sync","level":"debug","appender":"p2p"} {"name":"p2p","level":"debug","appender":"p2p"} -plugin = transaction_status_api -plugin = account_history_api -plugin = witness plugin = account_by_key -plugin = rc_api +plugin = state_snapshot plugin = wallet_bridge_api -plugin = account_history_rocksdb plugin = reputation_api -plugin = account_by_key_api -plugin = network_node_api -plugin = block_api -plugin = state_snapshot +plugin = transaction_status_api plugin = database_api plugin = debug_node_api +plugin = witness +plugin = account_history_rocksdb +plugin = account_history_api +plugin = rc_api +plugin = block_api +plugin = network_node_api +plugin = account_by_key_api shared-file-size = 128M p2p-endpoint = 0.0.0.0:0 webserver-http-endpoint = 0.0.0.0:0 diff --git a/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp b/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp index 832c1315b5..d6008c5db4 100644 --- a/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp +++ b/tests/clive-local-tools/clive_local_tools/testnet_block_log/timestamp @@ -1 +1 @@ -@2025-06-03 14:31:33+00:00 \ No newline at end of file +@2025-06-04 13:15:51+00:00 \ No newline at end of file -- GitLab From 75c29ed8761ad5955f9fc33ad1c65878c2b9ca82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Tue, 3 Jun 2025 14:47:31 +0200 Subject: [PATCH 15/17] Fix request_id of savings withdrawal operation in tests --- .../cli/accounts_validation/test_bad_accounts_validation.py | 3 ++- .../cli/accounts_validation/test_known_accounts_enabled.py | 4 +++- .../exchange_operations/test_force_required_operations.py | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py b/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py index 03fcadfca6..723ffc4f01 100644 --- a/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py +++ b/tests/functional/cli/accounts_validation/test_bad_accounts_validation.py @@ -244,7 +244,8 @@ async def test_no_validation_of_canceling_savings_withdrawal_to_account_that_bec ) -> None: """It should be possible to cancel savings withdrawal, even if account is on bad account list.""" # ARRANGE - request_id: int = 22 + request_id = 1 # there is already one withdrawal pending in the block log + cli_tester.process_savings_withdrawal( to=TEMPORARY_BAD_ACCOUNT, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, request_id=request_id ) diff --git a/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py b/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py index 1c4238b9d6..99eb22b9bb 100644 --- a/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py +++ b/tests/functional/cli/accounts_validation/test_known_accounts_enabled.py @@ -165,10 +165,12 @@ async def test_no_validation_of_canceling_savings_withdrawal_to_account_that_was ) -> None: """It should be possible to cancel savings withdrawal, even if account is not on known account list.""" # ARRANGE - request_id: int = 23 + request_id = 1 # there is already one withdrawal pending in the block log + cli_tester.process_savings_withdrawal( to=KNOWN_ACCOUNT, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, request_id=request_id ) + cli_tester.configure_known_account_remove(account_name=KNOWN_ACCOUNT) # ACT & ASSERT diff --git a/tests/functional/cli/exchange_operations/test_force_required_operations.py b/tests/functional/cli/exchange_operations/test_force_required_operations.py index 9672a1746c..fca6066785 100644 --- a/tests/functional/cli/exchange_operations/test_force_required_operations.py +++ b/tests/functional/cli/exchange_operations/test_force_required_operations.py @@ -40,7 +40,7 @@ def transaction_with_forceable_operation_path(tmp_path: Path) -> Path: to=KNOWN_EXCHANGE_NAME, amount=tt.Asset.Hive(1), memo="transfer_from_savings_operation forceable test", - request_id=1234, + request_id=1, # there is already one withdrawal pending in the block log ), ] return create_transaction_file(tmp_path, operations) @@ -94,8 +94,9 @@ async def test_validate_of_performing_recurrent_transfer_to_exchange(cli_tester: @pytest.mark.parametrize("force", [True, False]) async def test_validate_of_performing_withdrawing_to_exchange(cli_tester: CLITester, *, force: bool) -> None: # ARRANGE + request_id = 1 # there is already one withdrawal pending in the block log + def send_operation() -> None: - request_id: int = 4123 cli_tester.process_savings_withdrawal( to=KNOWN_EXCHANGE_NAME, amount=AMOUNT, sign=WORKING_ACCOUNT_KEY_ALIAS, force=force, request_id=request_id ) -- GitLab From 24cfd19ced94f0eff7de8d19a9dc9a7d7beda799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Tue, 3 Jun 2025 14:48:51 +0200 Subject: [PATCH 16/17] Rename param operations -> content --- tests/clive-local-tools/clive_local_tools/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/clive-local-tools/clive_local_tools/helpers.py b/tests/clive-local-tools/clive_local_tools/helpers.py index 81d6feef51..a445be0782 100644 --- a/tests/clive-local-tools/clive_local_tools/helpers.py +++ b/tests/clive-local-tools/clive_local_tools/helpers.py @@ -45,9 +45,9 @@ def get_formatted_error_message(error: ClickException) -> str: return capture.get() -def create_transaction_file(path: Path, operations: TransactionConvertibleType) -> Path: +def create_transaction_file(path: Path, content: TransactionConvertibleType) -> Path: transaction_path = path / "trx.json" - transaction = ensure_transaction(operations) + transaction = ensure_transaction(content) transaction_serialized = transaction.json(by_alias=True) transaction_path.write_text(transaction_serialized) return transaction_path -- GitLab From 462e72ce52738fca4e45b993d666f52acb420743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20K=C4=99dzierski?= Date: Fri, 6 Jun 2025 11:54:35 +0000 Subject: [PATCH 17/17] Move visit_account_witness_proxy_operation from FinancialOperationsAccountCollector to PotentialBadAccountCollector and PotentialKnownAccountCollector. Command 'clive process proxy set' is not a financial operation. --- .../financial_operations_account_collector.py | 6 ------ .../operation/potential_bad_account_collector.py | 10 +++++++++- .../operation/potential_known_account_collector.py | 9 ++++++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/clive/__private/visitors/operation/financial_operations_account_collector.py b/clive/__private/visitors/operation/financial_operations_account_collector.py index fb79ae9593..c8148b1d80 100644 --- a/clive/__private/visitors/operation/financial_operations_account_collector.py +++ b/clive/__private/visitors/operation/financial_operations_account_collector.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, override from clive.__private.core.constants.node import ( - CANCEL_PROXY_VALUE, PERCENT_TO_REMOVE_WITHDRAW_ROUTE, TRANSFER_TO_VESTING_RECEIVER_IS_FROM_VALUE, ) @@ -24,11 +23,6 @@ class FinancialOperationsAccountCollector(OperationVisitor): self.accounts: set[str] = set() """Names of accounts that are target of financial operation.""" - @override - def visit_account_witness_proxy_operation(self, operation: schemas.AccountWitnessProxyOperation) -> None: - if operation.proxy != CANCEL_PROXY_VALUE: - self.accounts.add(operation.proxy) - @override def visit_delegate_vesting_shares_operation(self, operation: schemas.DelegateVestingSharesOperation) -> None: if operation.vesting_shares not in DELEGATION_REMOVE_ASSETS: diff --git a/clive/__private/visitors/operation/potential_bad_account_collector.py b/clive/__private/visitors/operation/potential_bad_account_collector.py index d040a9eeae..6f9b506c54 100644 --- a/clive/__private/visitors/operation/potential_bad_account_collector.py +++ b/clive/__private/visitors/operation/potential_bad_account_collector.py @@ -1,7 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override +from clive.__private.core.constants.node import CANCEL_PROXY_VALUE from clive.__private.visitors.operation.financial_operations_account_collector import ( FinancialOperationsAccountCollector, ) @@ -9,9 +10,16 @@ from clive.__private.visitors.operation.financial_operations_account_collector i if TYPE_CHECKING: from collections.abc import Iterable + from clive.__private.models import schemas + class PotentialBadAccountCollector(FinancialOperationsAccountCollector): """Collects accounts that could potentially be bad basing on the operations that are made to them.""" def get_bad_accounts(self, bad_accounts: Iterable[str]) -> list[str]: return [account for account in self.accounts if account in bad_accounts] + + @override + def visit_account_witness_proxy_operation(self, operation: schemas.AccountWitnessProxyOperation) -> None: + if operation.proxy != CANCEL_PROXY_VALUE: + self.accounts.add(operation.proxy) diff --git a/clive/__private/visitors/operation/potential_known_account_collector.py b/clive/__private/visitors/operation/potential_known_account_collector.py index 007bbb2e3e..9208d14c9d 100644 --- a/clive/__private/visitors/operation/potential_known_account_collector.py +++ b/clive/__private/visitors/operation/potential_known_account_collector.py @@ -1,7 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override +from clive.__private.core.constants.node import CANCEL_PROXY_VALUE from clive.__private.visitors.operation.financial_operations_account_collector import ( FinancialOperationsAccountCollector, ) @@ -10,6 +11,7 @@ if TYPE_CHECKING: from collections.abc import Iterable from clive.__private.core.accounts.accounts import KnownAccount + from clive.__private.models import schemas class PotentialKnownAccountCollector(FinancialOperationsAccountCollector): @@ -18,3 +20,8 @@ class PotentialKnownAccountCollector(FinancialOperationsAccountCollector): def get_unknown_accounts(self, already_known_accounts: Iterable[KnownAccount]) -> list[str]: already_known_accounts_names = [account.name for account in already_known_accounts] return [account for account in self.accounts if account not in already_known_accounts_names] + + @override + def visit_account_witness_proxy_operation(self, operation: schemas.AccountWitnessProxyOperation) -> None: + if operation.proxy != CANCEL_PROXY_VALUE: + self.accounts.add(operation.proxy) -- GitLab