From 0c04d5c6dc4b1c6a5677e14cb6186621b8494117 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sat, 7 Dec 2024 02:15:26 +0100 Subject: [PATCH] chore: wip --- src/algokit_utils/accounts/account_manager.py | 13 +- src/algokit_utils/applications/app_client.py | 30 ++- .../applications/app_deployer.py | 94 ++++++---- src/algokit_utils/applications/app_factory.py | 73 ++++---- src/algokit_utils/applications/app_manager.py | 14 +- src/algokit_utils/applications/utils.py | 17 +- src/algokit_utils/models/abi.py | 4 +- .../transactions/transaction_composer.py | 135 +------------ .../transactions/transaction_sender.py | 7 +- tests/accounts/test_account_manager.py | 17 +- tests/applications/test_app_client.py | 23 ++- tests/applications/test_app_factory.py | 177 +++++++++++++++++- tests/applications/test_app_manager.py | 18 +- tests/assets/test_asset_manager.py | 29 +-- tests/conftest.py | 42 ----- tests/test_transaction_composer.py | 17 +- .../transactions/test_transaction_composer.py | 29 ++- .../transactions/test_transaction_creator.py | 17 +- tests/transactions/test_transaction_sender.py | 43 +++-- 19 files changed, 463 insertions(+), 336 deletions(-) diff --git a/src/algokit_utils/accounts/account_manager.py b/src/algokit_utils/accounts/account_manager.py index a4e13b5..ed4d045 100644 --- a/src/algokit_utils/accounts/account_manager.py +++ b/src/algokit_utils/accounts/account_manager.py @@ -106,6 +106,7 @@ def from_mnemonic(self, mnemonic: str) -> Account: private_key = to_private_key(mnemonic) account = Account(private_key=private_key) self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=private_key)) return account def from_environment(self, name: str, fund_with: AlgoAmount | None = None) -> Account: @@ -115,12 +116,14 @@ def from_environment(self, name: str, fund_with: AlgoAmount | None = None) -> Ac private_key = mnemonic.to_private_key(account_mnemonic) account = Account(private_key=private_key) self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=private_key)) return account if self._client_manager.is_local_net(): kmd_account = self._kmd_account_manager.get_or_create_wallet_account(name, fund_with) account = Account(private_key=kmd_account.private_key) self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=kmd_account.private_key)) return account raise ValueError(f"Missing environment variable {name.upper()}_MNEMONIC when looking for account {name}") @@ -134,11 +137,13 @@ def from_kmd( account = Account(private_key=kmd_account.private_key) self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=kmd_account.private_key)) return account - def rekeyed(self, sender: str, account: Account) -> Account: - self._accounts[sender] = account - return account + def rekeyed(self, sender: Account | str, account: Account) -> Account: + sender_address = sender.address if isinstance(sender, Account) else sender + self._accounts[sender_address] = account + return Account(address=sender_address, private_key=account.private_key) def random(self) -> Account: """ @@ -148,12 +153,14 @@ def random(self) -> Account: """ account = Account.new_account() self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=account.private_key)) return account def localnet_dispenser(self) -> Account: kmd_account = self._kmd_account_manager.get_localnet_dispenser_account() account = Account(private_key=kmd_account.private_key) self._accounts[account.address] = account + self.set_signer(account.address, AccountTransactionSigner(private_key=kmd_account.private_key)) return account def dispenser_from_environment(self) -> Account: diff --git a/src/algokit_utils/applications/app_client.py b/src/algokit_utils/applications/app_client.py index 4cb40c9..2caf9c6 100644 --- a/src/algokit_utils/applications/app_client.py +++ b/src/algokit_utils/applications/app_client.py @@ -4,7 +4,7 @@ import copy import json import os -from dataclasses import dataclass +from dataclasses import dataclass, fields from typing import TYPE_CHECKING, Any, Protocol, TypeVar import algosdk @@ -572,11 +572,29 @@ def delete(self, params: AppClientMethodCallParams) -> AppDeleteMethodCall: ) return AppDeleteMethodCall(**input_params) - def update(self, params: AppClientMethodCallParams) -> AppUpdateMethodCall: - input_params = self._get_abi_params( - params.__dict__, on_complete=algosdk.transaction.OnComplete.UpdateApplicationOC + def update( + self, params: AppClientMethodCallParams | AppClientMethodCallWithCompilationAndSendParams + ) -> AppUpdateMethodCall: + compile_params = ( + self._client.compile( + app_spec=self._client.app_spec, + app_manager=self._algorand.app, + deploy_time_params=params.deploy_time_params, + updatable=params.updatable, + deletable=params.deletable, + ).__dict__ + if isinstance(params, AppClientMethodCallWithCompilationAndSendParams) + else {} ) - return AppUpdateMethodCall(**input_params) + + input_params = { + **self._get_abi_params(params.__dict__, on_complete=algosdk.transaction.OnComplete.UpdateApplicationOC), + **compile_params, + } + # Filter input_params to include only fields valid for AppUpdateMethodCall + app_update_method_call_fields = {field.name for field in fields(AppUpdateMethodCall)} + filtered_input_params = {k: v for k, v in input_params.items() if k in app_update_method_call_fields} + return AppUpdateMethodCall(**filtered_input_params) def close_out(self, params: AppClientMethodCallParams) -> AppCallMethodCall: input_params = self._get_abi_params(params.__dict__, on_complete=algosdk.transaction.OnComplete.CloseOutOC) @@ -786,7 +804,7 @@ def call(self, params: AppClientMethodCallWithSendParams) -> SendAppTransactionR confirmations=simulate_response.confirmations, group_id=simulate_response.group_id or "", returns=simulate_response.returns, - return_value=simulate_response.returns[-1].return_value, + return_value=simulate_response.returns[-1], ) return self._client._handle_call_errors( diff --git a/src/algokit_utils/applications/app_deployer.py b/src/algokit_utils/applications/app_deployer.py index 23c9e1a..7b3ce5f 100644 --- a/src/algokit_utils/applications/app_deployer.py +++ b/src/algokit_utils/applications/app_deployer.py @@ -5,7 +5,7 @@ from typing import Literal import algosdk -from algosdk.atomic_transaction_composer import TransactionSigner +from algosdk.atomic_transaction_composer import ABIResult, TransactionSigner from algosdk.logic import get_application_address from algosdk.transaction import OnComplete, Transaction from algosdk.v2client.indexer import IndexerClient @@ -20,7 +20,6 @@ ) from algokit_utils.applications.app_manager import AppManager, BoxReference, TealTemplateParams from algokit_utils.config import config -from algokit_utils.models.abi import ABIValue from algokit_utils.transactions.transaction_composer import ( AppCreateMethodCall, AppCreateParams, @@ -113,11 +112,15 @@ class AppDeployResult: app_id: int | None = None app_address: str | None = None transaction: Transaction | None = None + tx_id: str | None = None + transactions: list[Transaction] | None = None + tx_ids: list[str] | None = None confirmation: algosdk.v2client.algod.AlgodResponseType | None = None + confirmations: list[algosdk.v2client.algod.AlgodResponseType] | None = None compiled_approval: dict | None = None compiled_clear: dict | None = None - return_value: ABIValue | None = None - delete_return: ABIValue | None = None + return_value: ABIResult | None = None + delete_return_value: ABIResult | None = None delete_result: ConfirmedTransactionResult | None = None @@ -319,8 +322,12 @@ def _create_app( return AppDeployResult( **app_metadata_dict, + tx_id=result.tx_id, + tx_ids=result.tx_ids, transaction=result.transaction, + transactions=result.transactions, confirmation=result.confirmation, + confirmations=result.confirmations, return_value=result.return_value, ) @@ -342,7 +349,7 @@ def _handle_schema_break( return self._create_app(deployment, approval_program, clear_program) if existing_app.deletable: - return self._create_and_delete_app(deployment, existing_app, approval_program, clear_program) + return self._replace_app(deployment, existing_app, approval_program, clear_program) else: raise ValueError("App is not deletable but onSchemaBreak=ReplaceApp, " "cannot delete and recreate app") @@ -369,13 +376,13 @@ def _handle_update( if deployment.on_update in (OnUpdate.ReplaceApp, "replace"): if existing_app.deletable: - return self._create_and_delete_app(deployment, existing_app, approval_program, clear_program) + return self._replace_app(deployment, existing_app, approval_program, clear_program) else: raise ValueError("App is not deletable but onUpdate=ReplaceApp, " "cannot delete and recreate app") raise ValueError(f"Unsupported onUpdate value: {deployment.on_update}") - def _create_and_delete_app( + def _replace_app( self, deployment: AppDeployParams, existing_app: AppMetaData, @@ -386,33 +393,35 @@ def _create_and_delete_app( # Add create transaction if isinstance(deployment.create_params, AppCreateMethodCall): - create_params = AppCreateMethodCall( - **{ - **deployment.create_params.__dict__, - "approval_program": approval_program, - "clear_state_program": clear_program, - } + composer.add_app_create_method_call( + AppCreateMethodCall( + **{ + **deployment.create_params.__dict__, + "approval_program": approval_program, + "clear_state_program": clear_program, + } + ) ) - composer.add_app_create_method_call(create_params) else: - create_params = AppCreateParams( - **{ - **deployment.create_params.__dict__, - "approval_program": approval_program, - "clear_state_program": clear_program, - } + composer.add_app_create( + AppCreateParams( + **{ + **deployment.create_params.__dict__, + "approval_program": approval_program, + "clear_state_program": clear_program, + } + ) ) - composer.add_app_create(create_params) # Add delete transaction if isinstance(deployment.delete_params, AppDeleteMethodCall): - delete_params = AppDeleteMethodCall( + delete_call_params = AppDeleteMethodCall( **{ **deployment.delete_params.__dict__, "app_id": existing_app.app_id, } ) - composer.add_app_delete_method_call(delete_params) + composer.add_app_delete_method_call(delete_call_params) else: delete_params = AppDeleteParams( **{ @@ -424,30 +433,43 @@ def _create_and_delete_app( result = composer.send() - app_id = int(result.confirmations[0]["application-index"]) + app_id = int(result.confirmations[0]["application-index"]) # type: ignore[call-overload] app_metadata = AppMetaData( app_id=app_id, app_address=get_application_address(app_id), **deployment.metadata.__dict__, created_metadata=deployment.metadata, - created_round=result.confirmations[0]["confirmed-round"], - updated_round=result.confirmations[0]["confirmed-round"], + created_round=result.confirmations[0]["confirmed-round"], # type: ignore[call-overload] + updated_round=result.confirmations[0]["confirmed-round"], # type: ignore[call-overload] deleted=False, ) self._update_app_lookup(deployment.create_params.sender, app_metadata) + app_metadata_dict = app_metadata.__dict__ + app_metadata_dict["operation_performed"] = OperationPerformed.Replace + app_metadata_dict["app_id"] = app_id + app_metadata_dict["app_address"] = get_application_address(app_id) + + # Extract return_value and delete_return_value from ABIResult + return_value = result.returns[0] if result.returns and isinstance(result.returns[0], ABIResult) else None + delete_return_value = ( + result.returns[-1] if len(result.returns) > 1 and isinstance(result.returns[-1], ABIResult) else None + ) + return AppDeployResult( - operation_performed="replace", - app_id=app_id, - app_address=get_application_address(app_id), + **app_metadata_dict, + tx_id=result.tx_ids[0], + tx_ids=result.tx_ids, transaction=result.transactions[0], + transactions=result.transactions, confirmation=result.confirmations[0], - return_value=result.returns[0] if result.returns else None, - delete_return=result.returns[-1] if len(result.returns) > 1 else None, - delete_result={ - "transaction": result.transactions[-1], - "confirmation": result.confirmations[-1], - }, + confirmations=result.confirmations, + return_value=return_value, + delete_return_value=delete_return_value, + delete_result=ConfirmedTransactionResult( + transaction=result.transactions[-1], + confirmation=result.confirmations[-1], + ), ) def _update_app( @@ -498,7 +520,9 @@ def _update_app( **app_metadata.__dict__, operation_performed=OperationPerformed.Update, transaction=result.transaction, + transactions=result.transactions, confirmation=result.confirmation, + confirmations=result.confirmations, return_value=result.return_value, ) diff --git a/src/algokit_utils/applications/app_factory.py b/src/algokit_utils/applications/app_factory.py index 4b1763e..d2c8277 100644 --- a/src/algokit_utils/applications/app_factory.py +++ b/src/algokit_utils/applications/app_factory.py @@ -6,7 +6,7 @@ import algosdk from algosdk import transaction from algosdk.abi import Method -from algosdk.atomic_transaction_composer import TransactionSigner +from algosdk.atomic_transaction_composer import ABIResult, TransactionSigner from algosdk.source_map import SourceMap from algosdk.transaction import OnComplete, Transaction @@ -22,7 +22,12 @@ AppSourceMaps, ExposedLogicErrorDetails, ) -from algokit_utils.applications.app_deployer import AppDeployParams, DeployAppDeleteParams, DeployAppUpdateParams +from algokit_utils.applications.app_deployer import ( + AppDeployParams, + ConfirmedTransactionResult, + DeployAppDeleteParams, + DeployAppUpdateParams, +) from algokit_utils.applications.app_manager import TealTemplateParams from algokit_utils.applications.utils import ( get_abi_decoded_value, @@ -30,6 +35,7 @@ get_arc56_method, get_arc56_return_value, ) +from algokit_utils.models.abi import ABIStruct, ABIValue from algokit_utils.models.application import ( DELETABLE_TEMPLATE_NAME, UPDATABLE_TEMPLATE_NAME, @@ -47,7 +53,7 @@ AppUpdateMethodCall, BuiltTransactions, ) -from algokit_utils.transactions.transaction_sender import SendAppTransactionResult +from algokit_utils.transactions.transaction_sender import SendAppCreateTransactionResult, SendAppTransactionResult T = TypeVar("T") @@ -119,11 +125,12 @@ class AppFactoryDeployResult: created_round: int deletable: bool deleted: bool - delete_return: Any | None = None + delete_return_value: ABIValue | ABIStruct | None = None + delete_result: ConfirmedTransactionResult | None = None group_id: str | None = None name: str operation_performed: OperationPerformed - return_value: Any | None = None + return_value: ABIValue | ABIStruct | None = None returns: list[Any] | None = None transaction: Transaction transactions: list[Transaction] @@ -142,7 +149,7 @@ def __init__(self, factory: "AppFactory") -> None: def create(self, params: AppFactoryCreateParams | None = None) -> AppCreateParams: create_args = {} if params: - create_args = {**params.__dict__} + create_args = {**params.__dict__.copy()} del create_args["schema"] del create_args["sender"] del create_args["on_complete"] @@ -214,15 +221,15 @@ def create(self, params: AppFactoryCreateMethodCallParams) -> AppCreateMethodCal ) def deploy_update(self, params: AppClientMethodCallParams) -> AppUpdateMethodCall: - params_dict = params.__dict__ + params_dict = params.__dict__.copy() params_dict["sender"] = self._factory._get_sender(params.sender) params_dict["method"] = get_arc56_method(params.method, self._factory._app_spec) params_dict["args"] = self._factory._get_create_abi_args_with_default_values(params.method, params.args) params_dict["on_complete"] = OnComplete.UpdateApplicationOC - return AppUpdateMethodCall(**params.__dict__, app_id=0, approval_program="", clear_state_program="") + return AppUpdateMethodCall(**params_dict, app_id=0, approval_program="", clear_state_program="") def deploy_delete(self, params: AppClientMethodCallParams) -> AppDeleteMethodCall: - params_dict = params.__dict__ + params_dict = params.__dict__.copy() params_dict["sender"] = self._factory._get_sender(params.sender) params_dict["method"] = get_arc56_method(params.method, self._factory._app_spec) params_dict["args"] = self._factory._get_create_abi_args_with_default_values(params.method, params.args) @@ -311,7 +318,7 @@ def __init__(self, factory: "AppFactory") -> None: def bare(self) -> _AppFactoryBareSendAccessor: return self._bare - def create(self, params: AppFactoryCreateMethodCallParams) -> tuple[AppClient, AppFactoryDeployResult]: + def create(self, params: AppFactoryCreateMethodCallParams) -> tuple[AppClient, SendAppCreateTransactionResult]: updatable = params.updatable if params.updatable is not None else self._factory._updatable deletable = params.deletable if params.deletable is not None else self._factory._deletable deploy_time_params = ( @@ -326,16 +333,13 @@ def create(self, params: AppFactoryCreateMethodCallParams) -> tuple[AppClient, A ) ) + create_params_dict = params.__dict__.copy() + create_params_dict["updatable"] = updatable + create_params_dict["deletable"] = deletable + create_params_dict["deploy_time_params"] = deploy_time_params result = self._factory._handle_call_errors( lambda: self._algorand.send.app_create_method_call( - self._factory.params.create( - AppFactoryCreateMethodCallParams( - **params.__dict__, - updatable=updatable, - deletable=deletable, - deploy_time_params=deploy_time_params, - ) - ) + self._factory.params.create(AppFactoryCreateMethodCallParams(**create_params_dict)) ) ) @@ -343,7 +347,16 @@ def create(self, params: AppFactoryCreateMethodCallParams) -> tuple[AppClient, A self._factory.get_app_client_by_id( app_id=result.app_id, ), - AppFactoryDeployResult(**{**result.__dict__, **(compiled.__dict__ if compiled else {})}), + SendAppCreateTransactionResult( + **{ + **result.__dict__, + **( + {"compiled_approval": compiled.compiled_approval, "compiled_clear": compiled.compiled_clear} + if compiled + else {} + ), + } + ), ) @@ -492,8 +505,8 @@ def _is_method_call_params( result = {**deploy_result.__dict__, **(compiled.__dict__ if compiled else {})} - if hasattr(result, "return_value"): - if result["operation_performed"] == "update": + if "return_value" in result: + if result["operation_performed"] == OperationPerformed.Update: if update_params and hasattr(update_params, "method"): result["return_value"] = get_arc56_return_value( result["return_value"], @@ -507,18 +520,13 @@ def _is_method_call_params( self._app_spec.structs, ) - if "delete_return" in result and delete_params and hasattr(delete_params, "method"): - result["delete_return"] = get_arc56_return_value( - result["delete_return"], + if "delete_return_value" in result and delete_params and hasattr(delete_params, "method"): + result["delete_return_value"] = get_arc56_return_value( + result["delete_return_value"], get_arc56_method(delete_params.method, self._app_spec), # type: ignore[arg-type] self._app_spec.structs, ) - del result["delete_result"] - result["transactions"] = [] - result["tx_id"] = "" - result["tx_ids"] = [] - return app_client, AppFactoryDeployResult(**result) def get_app_client_by_id( @@ -624,13 +632,12 @@ def _handle_call_errors(self, call: Callable[[], T]) -> T: raise self.expose_logic_error(e) from None def _parse_method_call_return(self, result: SendAppTransactionResult, method: Method) -> SendAppTransactionResult: - return_value = result.return_value - if isinstance(return_value, dict): - return_value = get_arc56_return_value(return_value, method, self._app_spec.structs) return SendAppTransactionResult( **{ **result.__dict__, - "return_value": return_value, + "return_value": get_arc56_return_value(result.return_value, method, self._app_spec.structs) + if isinstance(result.return_value, ABIResult) + else None, } ) diff --git a/src/algokit_utils/applications/app_manager.py b/src/algokit_utils/applications/app_manager.py index 09f6028..8cf1ada 100644 --- a/src/algokit_utils/applications/app_manager.py +++ b/src/algokit_utils/applications/app_manager.py @@ -2,12 +2,12 @@ from collections.abc import Mapping from dataclasses import dataclass from enum import IntEnum -from typing import Any, TypeAlias +from typing import Any, TypeAlias, cast import algosdk import algosdk.atomic_transaction_composer import algosdk.box_reference -from algosdk.atomic_transaction_composer import AccountTransactionSigner +from algosdk.atomic_transaction_composer import ABIResult, AccountTransactionSigner from algosdk.box_reference import BoxReference as AlgosdkBoxReference from algosdk.logic import get_application_address from algosdk.v2client import algod @@ -266,7 +266,9 @@ def get_box_reference(box_id: BoxIdentifier | BoxReference) -> tuple[int, bytes] elif isinstance(box_id, bytes): name = box_id elif isinstance(box_id, AccountTransactionSigner): - name = algosdk.encoding.decode_address(algosdk.account.address_from_private_key(box_id.private_key)) + name = cast( + bytes, algosdk.encoding.decode_address(algosdk.account.address_from_private_key(box_id.private_key)) + ) else: raise ValueError(f"Invalid box identifier type: {type(box_id)}") @@ -275,7 +277,7 @@ def get_box_reference(box_id: BoxIdentifier | BoxReference) -> tuple[int, bytes] @staticmethod def get_abi_return( confirmation: algosdk.v2client.algod.AlgodResponseType, method: algosdk.abi.Method | None = None - ) -> ABIValue | None: + ) -> ABIResult | None: """Get the ABI return value from a transaction confirmation.""" if not method: return None @@ -291,7 +293,7 @@ def get_abi_return( if not abi_result: return None - return abi_result.return_value # type: ignore[no-any-return] + return abi_result @staticmethod def decode_app_state(state: list[dict[str, Any]]) -> dict[str, AppState]: @@ -350,7 +352,7 @@ def replace_template_variables(program: str, template_values: TealTemplateParams return "\n".join(program_lines) @staticmethod - def replace_teal_template_deploy_time_control_params(teal_template_code: str, params: dict[str, bool]) -> str: + def replace_teal_template_deploy_time_control_params(teal_template_code: str, params: Mapping[str, bool]) -> str: if params.get("updatable") is not None: if UPDATABLE_TEMPLATE_NAME not in teal_template_code: raise ValueError( diff --git a/src/algokit_utils/applications/utils.py b/src/algokit_utils/applications/utils.py index 88991d1..37c08a7 100644 --- a/src/algokit_utils/applications/utils.py +++ b/src/algokit_utils/applications/utils.py @@ -3,6 +3,7 @@ from algosdk.abi import Method as AlgorandABIMethod from algosdk.abi import TupleType +from algosdk.atomic_transaction_composer import ABIResult from algokit_utils._legacy_v2.application_specification import ( ApplicationSpecification, @@ -61,10 +62,10 @@ def get_arc56_method(method_name_or_signature: str, app_spec: Arc56Contract) -> def get_arc56_return_value( - return_value: dict[str, Any] | None, + return_value: ABIResult | None, method: Method | AlgorandABIMethod, structs: dict[str, list[StructField]], -) -> Any: # noqa: ANN401 +) -> ABIValue | ABIStruct | None: """Checks for decode errors on the return value and maps it to the specified type. Args: @@ -92,11 +93,11 @@ def get_arc56_return_value( return None # Handle decode errors - if return_value.get("decode_error"): - raise ValueError(return_value["decode_error"]) + if return_value.decode_error: + raise ValueError(return_value.decode_error) # Get raw return value - raw_value = return_value.get("raw_return_value") + raw_value = return_value.raw_value # Handle AVM types if type_str == "AVMBytes": @@ -104,15 +105,15 @@ def get_arc56_return_value( if type_str == "AVMString" and raw_value: return raw_value.decode("utf-8") if type_str == "AVMUint64" and raw_value: - return ABIType.from_string("uint64").decode(raw_value) + return ABIType.from_string("uint64").decode(raw_value) # type: ignore[no-any-return] # Handle structs if struct and struct in structs: - return_tuple = return_value.get("return_value") + return_tuple = return_value.return_value return get_abi_struct_from_abi_tuple(return_tuple, structs[struct], structs) # Return as-is - return return_value.get("return_value") + return return_value.return_value # type: ignore[no-any-return] def get_abi_encoded_value(value: Any, type_str: str, structs: dict[str, list[StructField]]) -> bytes: # noqa: ANN401, PLR0911 diff --git a/src/algokit_utils/models/abi.py b/src/algokit_utils/models/abi.py index a9c90e3..4e83727 100644 --- a/src/algokit_utils/models/abi.py +++ b/src/algokit_utils/models/abi.py @@ -7,8 +7,8 @@ ABIPrimitiveValue = bool | int | str | bytes | bytearray # NOTE: This is present in js-algorand-sdk, but sadly not in untyped py-algorand-sdk -ABIValue = ABIPrimitiveValue | list["ABIValue"] | tuple["ABIValue"] | dict[str, "ABIValue"] -ABIStruct = dict[str, list[StructField]] +ABIValue: TypeAlias = ABIPrimitiveValue | list["ABIValue"] | tuple["ABIValue"] | dict[str, "ABIValue"] +ABIStruct: TypeAlias = dict[str, list[StructField]] ABIType: TypeAlias = algosdk.abi.ABIType diff --git a/src/algokit_utils/transactions/transaction_composer.py b/src/algokit_utils/transactions/transaction_composer.py index 7d815c5..84e9da5 100644 --- a/src/algokit_utils/transactions/transaction_composer.py +++ b/src/algokit_utils/transactions/transaction_composer.py @@ -606,7 +606,7 @@ def send_atomic_transaction_composer( # noqa: C901, PLR0912 confirmations=confirmations or [], tx_ids=[t.get_txid() for t in transactions_to_send], transactions=transactions_to_send, - returns=[r.return_value for r in result.abi_results], + returns=result.abi_results, ) except AlgodHTTPError as e: @@ -1013,6 +1013,10 @@ def _build_method_call( # noqa: C901, PLR0912 method_args.append(arg) continue + if isinstance(arg, TransactionWithSigner): + method_args.append(arg) + continue + if algosdk.abi.is_abi_transaction_type(params.method.args[i + arg_offset].type): match arg: case ( @@ -1073,138 +1077,11 @@ def _build_method_call( # noqa: C901, PLR0912 else None, approval_program=params.approval_program if hasattr(params, "approval_program") else None, # type: ignore[arg-type] clear_program=params.clear_state_program if hasattr(params, "clear_state_program") else None, # type: ignore[arg-type] + rekey_to=params.rekey_to, ) return self._build_atc(method_atc) - # TODO: reconsider whether atc's add_method_call is the best way to handle passing manually encoded abi args - # def _build_method_call( - # self, params: MethodCallParams, suggested_params: algosdk.transaction.SuggestedParams - # ) -> list[TransactionWithSigner]: - # # Initialize lists to store transactions and encoded arguments - # method_args: list[ABIValue | TransactionWithSigner] = [] - # arg_offset = 0 - - # # Initialize foreign arrays - # accounts = params.account_references[:] if params.account_references else [] - # foreign_apps = params.app_references[:] if params.app_references else [] - # foreign_assets = params.asset_references[:] if params.asset_references else [] - # boxes = params.box_references[:] if params.box_references else [] - - # # Prepare app args starting with method selector - # encoded_args = [] - # encoded_args.append(params.method.get_selector()) - - # # Process method arguments - # if params.args: - # for i, arg in enumerate(params.args): - # if self._is_abi_value(arg): - # method_args.append(arg) - # continue - - # if algosdk.abi.is_abi_transaction_type(params.method.args[i + arg_offset].type): - # match arg: - # case ( - # AppCreateMethodCall() - # | AppCallMethodCall() - # | AppUpdateMethodCall() - # | AppDeleteMethodCall() - # ): - # temp_txn_with_signers = self._build_method_call(arg, suggested_params) - # method_args.extend(temp_txn_with_signers) - # arg_offset += len(temp_txn_with_signers) - 1 - # continue - # case AppCallParams(): - # txn = self._build_app_call(arg, suggested_params) - # case PaymentParams(): - # txn = self._build_payment(arg, suggested_params) - # case AssetOptInParams(): - # txn = self._build_asset_transfer( - # AssetTransferParams(**arg.__dict__, receiver=arg.sender, amount=0), suggested_params - # ) - # case _: - # raise ValueError(f"Unsupported method arg transaction type: {arg!s}") - - # method_args.append( - # TransactionWithSigner(txn=txn, signer=params.signer or self.get_signer(params.sender)) - # ) - # continue - - # # Handle ABI reference types - # if algosdk.abi.is_abi_reference_type(params.method.args[i + arg_offset].type): - # arg_type = params.method.args[i + arg_offset].type - # if arg_type == algosdk.abi.ABIReferenceType.ACCOUNT: - # address_type = algosdk.abi.AddressType() - # account_arg = address_type.decode(address_type.encode(cast(str | bytes, arg))) - # current_arg = algosdk.atomic_transaction_composer.populate_foreign_array( - # account_arg, accounts, params.sender - # ) - # method_args.append(current_arg) - # elif arg_type == algosdk.abi.ABIReferenceType.ASSET: - # asset_arg = int(cast(int, arg)) - # current_arg = algosdk.atomic_transaction_composer.populate_foreign_array( - # asset_arg, foreign_assets - # ) - # method_args.append(current_arg) - # elif arg_type == algosdk.abi.ABIReferenceType.APPLICATION: - # app_arg = int(cast(int, arg)) - # current_arg = algosdk.atomic_transaction_composer.populate_foreign_array( - # app_arg, foreign_apps, params.app_id - # ) - # method_args.append(current_arg) - # else: - # raise ValueError(f"Unsupported ABI reference type: {arg_type}") - # continue - - # # Regular ABI value - # method_args.append(arg) - - # # Encode regular ABI arguments - # for i, arg in enumerate(method_args): - # if isinstance(arg, TransactionWithSigner): - # continue - # arg_type = params.method.args[i].type - # if isinstance(arg_type, algosdk.abi.ABIType): - # try: - # encoded_args.append(arg_type.encode(arg)) - # except Exception as e: - # if ( - # isinstance(e, AttributeError) - # and isinstance(arg_type, algosdk.abi.StringType) - # and isinstance(arg, bytes) - # ): - # # Assume user passed a manually abi encoded string, ignore re-encoding and append as raw bytes - # encoded_args.append(arg) - # else: - # raise ValueError(f"Error encoding argument {arg} of type {arg_type}") from e - - # # Create the app call transaction - # txn = algosdk.transaction.ApplicationCallTxn( - # sender=params.sender, - # sp=suggested_params, - # index=params.app_id or 0, - # on_complete=params.on_complete or algosdk.transaction.OnComplete.NoOpOC, - # app_args=encoded_args, - # accounts=accounts, - # foreign_apps=foreign_apps, - # foreign_assets=foreign_assets, - # boxes=[AppManager.get_box_reference(ref) for ref in boxes] if boxes else None, - # note=params.note, - # lease=params.lease, - # ) - - # result = [TransactionWithSigner(txn=txn, signer=params.signer or self.get_signer(params.sender))] - - # # Add any transaction arguments - # for arg in method_args: - # if isinstance(arg, TransactionWithSigner): - # result.append(arg) - - # # Store the method for this transaction - # self.txn_method_map[txn.get_txid()] = params.method - - # return result - def _build_payment( self, params: PaymentParams, suggested_params: algosdk.transaction.SuggestedParams ) -> algosdk.transaction.Transaction: diff --git a/src/algokit_utils/transactions/transaction_sender.py b/src/algokit_utils/transactions/transaction_sender.py index cfb4682..cdc2b01 100644 --- a/src/algokit_utils/transactions/transaction_sender.py +++ b/src/algokit_utils/transactions/transaction_sender.py @@ -5,12 +5,11 @@ import algosdk import algosdk.atomic_transaction_composer -from algosdk.atomic_transaction_composer import AtomicTransactionResponse +from algosdk.atomic_transaction_composer import ABIResult, AtomicTransactionResponse from algosdk.transaction import Transaction from algokit_utils.applications.app_manager import AppManager from algokit_utils.assets.asset_manager import AssetManager -from algokit_utils.models.abi import ABIValue from algokit_utils.transactions.transaction_composer import ( AppCallMethodCall, AppCallParams, @@ -43,6 +42,7 @@ class SendSingleTransactionResult: # Fields from SendAtomicTransactionComposerResults group_id: str + tx_id: str | None = None tx_ids: list[str] # Full array of transaction IDs transactions: list[Transaction] confirmations: list[algosdk.v2client.algod.AlgodResponseType] @@ -56,7 +56,7 @@ class SendSingleAssetCreateTransactionResult(SendSingleTransactionResult): @dataclass(frozen=True) class SendAppTransactionResult(SendSingleTransactionResult): - return_value: ABIValue | None = None + return_value: ABIResult | None = None @dataclass(frozen=True) @@ -119,6 +119,7 @@ def send_transaction(params: T) -> SendSingleTransactionResult: **raw_result_dict, confirmation=raw_result.confirmations[-1], transaction=raw_result.transactions[-1], + tx_id=raw_result.tx_ids[-1], ) if post_log: diff --git a/tests/accounts/test_account_manager.py b/tests/accounts/test_account_manager.py index e8f1335..ec56a00 100644 --- a/tests/accounts/test_account_manager.py +++ b/tests/accounts/test_account_manager.py @@ -8,10 +8,19 @@ @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account def test_new_account_is_retrieved_and_funded(algorand: AlgorandClient) -> None: diff --git a/tests/applications/test_app_client.py b/tests/applications/test_app_client.py index 9256526..d4442c8 100644 --- a/tests/applications/test_app_client.py +++ b/tests/applications/test_app_client.py @@ -25,10 +25,19 @@ @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture @@ -573,11 +582,13 @@ def test_abi_with_default_arg_method( AppClientMethodCallWithSendParams(method=method_signature, args=[defined_value]) ) - assert defined_value_result.return_value == "Local state, defined value" + assert defined_value_result.return_value + assert defined_value_result.return_value.return_value == "Local state, defined value" # Test with default value default_value_result = app_client.send.call(AppClientMethodCallWithSendParams(method=method_signature, args=[None])) - assert default_value_result.return_value == "Local state, banana" + assert default_value_result.return_value + assert default_value_result.return_value.return_value == "Local state, banana" def test_exposing_logic_error(test_app_client_with_sourcemaps: AppClient) -> None: diff --git a/tests/applications/test_app_factory.py b/tests/applications/test_app_factory.py index 0de8b34..2fc5a2a 100644 --- a/tests/applications/test_app_factory.py +++ b/tests/applications/test_app_factory.py @@ -6,18 +6,36 @@ from algosdk.transaction import ApplicationCallTxn, ApplicationCreateTxn, OnComplete from algokit_utils._legacy_v2.deploy import OnSchemaBreak, OnUpdate, OperationPerformed -from algokit_utils.applications.app_client import AppClientMethodCallParams -from algokit_utils.applications.app_factory import AppFactory, AppFactoryCreateWithSendParams +from algokit_utils.applications.app_client import ( + AppClientMethodCallParams, + AppClientMethodCallWithCompilationAndSendParams, + AppClientMethodCallWithSendParams, +) +from algokit_utils.applications.app_factory import ( + AppFactory, + AppFactoryCreateMethodCallParams, + AppFactoryCreateWithSendParams, +) from algokit_utils.clients.algorand_client import AlgorandClient from algokit_utils.models.account import Account from algokit_utils.models.amount import AlgoAmount +from algokit_utils.transactions.transaction_composer import PaymentParams @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture @@ -212,7 +230,146 @@ def test_deploy_app_replace(factory: AppFactory) -> None: assert replaced_app.app_id > created_app.app_id assert replaced_app.app_address == algosdk.logic.get_application_address(replaced_app.app_id) assert replaced_app.confirmation is not None - assert replaced_app.delete_return is not None - assert replaced_app.delete_return.confirmation is not None - assert replaced_app.delete_return.transaction.application_id == created_app.app_id # type: ignore[union-attr] - assert replaced_app.delete_return.transaction.on_complete == OnComplete.DeleteApplicationOC # type: ignore[union-attr] + assert replaced_app.delete_result is not None + assert replaced_app.delete_result.confirmation is not None + assert len(replaced_app.transactions) == 2 # noqa: PLR2004 + assert isinstance(replaced_app.delete_result.transaction, ApplicationCallTxn) + assert replaced_app.delete_result.transaction.index == created_app.app_id # type: ignore[union-attr] + assert replaced_app.delete_result.transaction.on_complete == OnComplete.DeleteApplicationOC # type: ignore[union-attr] + + +def test_deploy_app_replace_abi(factory: AppFactory) -> None: + _, created_app = factory.deploy( + deploy_time_params={ + "VALUE": 1, + }, + deletable=True, + populate_app_call_resources=False, + ) + + _, replaced_app = factory.deploy( + deploy_time_params={ + "VALUE": 2, + }, + on_update=OnUpdate.ReplaceApp, + create_params=AppClientMethodCallParams(method="create_abi", args=["arg_io"]), + delete_params=AppClientMethodCallParams(method="delete_abi", args=["arg2_io"]), + ) + + assert replaced_app.operation_performed == OperationPerformed.Replace + assert replaced_app.app_id > created_app.app_id + assert replaced_app.app_address == algosdk.logic.get_application_address(replaced_app.app_id) + assert replaced_app.confirmation is not None + assert replaced_app.delete_result is not None + assert replaced_app.delete_result.confirmation is not None + assert len(replaced_app.transactions) == 2 + assert isinstance(replaced_app.delete_result.transaction, ApplicationCallTxn) + assert replaced_app.delete_result.transaction.index == created_app.app_id # type: ignore[union-attr] + assert replaced_app.delete_result.transaction.on_complete == OnComplete.DeleteApplicationOC # type: ignore[union-attr] + assert replaced_app.return_value == "arg_io" + assert replaced_app.delete_return_value == "arg2_io" + + +def test_create_then_call_app(factory: AppFactory) -> None: + app_client, _ = factory.send.bare.create( + AppFactoryCreateWithSendParams( + deploy_time_params={ + "UPDATABLE": 1, + "DELETABLE": 1, + "VALUE": 1, + }, + ) + ) + + call = app_client.send.call(AppClientMethodCallWithSendParams(method="call_abi", args=["test"])) + + assert call.return_value + assert call.return_value.return_value == "Hello, test" + + +def test_call_app_with_rekey(funded_account: Account, algorand: AlgorandClient, factory: AppFactory) -> None: + rekey_to = algorand.account.random() + + app_client, _ = factory.send.bare.create( + AppFactoryCreateWithSendParams( + deploy_time_params={ + "UPDATABLE": 1, + "DELETABLE": 1, + "VALUE": 1, + }, + ) + ) + + app_client.send.opt_in(AppClientMethodCallWithSendParams(method="opt_in", rekey_to=rekey_to.address)) + + # If the rekey didn't work this will throw + rekeyed_account = algorand.account.rekeyed(funded_account.address, rekey_to) + algorand.send.payment( + PaymentParams(amount=AlgoAmount.from_algo(0), sender=rekeyed_account.address, receiver=funded_account.address) + ) + + +def test_create_app_with_abi(factory: AppFactory) -> None: + _, call_return = factory.send.create( + AppFactoryCreateMethodCallParams( + method="create_abi", + args=["string_io"], + deploy_time_params={ + "UPDATABLE": 0, + "DELETABLE": 0, + "VALUE": 1, + }, + ) + ) + + assert call_return.return_value + # Fix return value issues + assert call_return.return_value.return_value == "string_io" + + +def test_update_app_with_abi(factory: AppFactory) -> None: + deploy_time_params = { + "UPDATABLE": 1, + "DELETABLE": 0, + "VALUE": 1, + } + app_client, _ = factory.send.bare.create( + AppFactoryCreateWithSendParams( + deploy_time_params=deploy_time_params, + ) + ) + + call_return = app_client.send.update( + AppClientMethodCallWithCompilationAndSendParams( + method="update_abi", + args=["string_io"], + deploy_time_params=deploy_time_params, + ) + ) + + assert call_return.return_value is not None + assert call_return.return_value.return_value == "string_io" + # TODO: fix this + # assert call_return.compiled_approval is not None + + +def test_delete_app_with_abi(factory: AppFactory) -> None: + app_client, _ = factory.send.bare.create( + AppFactoryCreateWithSendParams( + deploy_time_params={ + "UPDATABLE": 0, + "DELETABLE": 1, + "VALUE": 1, + }, + ) + ) + + call_return = app_client.send.delete( + AppClientMethodCallWithSendParams( + method="delete_abi", + args=["string_io"], + ) + ) + + assert call_return.return_value is not None + assert call_return.return_value.return_value == "string_io" diff --git a/tests/applications/test_app_manager.py b/tests/applications/test_app_manager.py index c10508c..57313d3 100644 --- a/tests/applications/test_app_manager.py +++ b/tests/applications/test_app_manager.py @@ -3,14 +3,24 @@ from algokit_utils.applications.app_manager import AppManager from algokit_utils.clients.algorand_client import AlgorandClient from algokit_utils.models.account import Account +from algokit_utils.models.amount import AlgoAmount from tests.conftest import check_output_stability @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account def test_template_substitution() -> None: diff --git a/tests/assets/test_asset_manager.py b/tests/assets/test_asset_manager.py index 8d2c6e8..2d5ea4e 100644 --- a/tests/assets/test_asset_manager.py +++ b/tests/assets/test_asset_manager.py @@ -1,8 +1,7 @@ -import algosdk import pytest from algosdk.atomic_transaction_composer import AccountTransactionSigner -from algokit_utils import Account, get_account +from algokit_utils import Account from algokit_utils.assets.asset_manager import ( AccountAssetInformation, AssetInformation, @@ -14,24 +13,32 @@ AssetCreateParams, PaymentParams, ) -from tests.conftest import get_unique_name @pytest.fixture -def sender(funded_account: Account) -> Account: - return funded_account +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() @pytest.fixture -def receiver(algod_client: algosdk.v2client.algod.AlgodClient) -> Account: - return get_account(algod_client, get_unique_name()) +def sender(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def receiver(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + return new_account def test_get_by_id(algorand: AlgorandClient, sender: Account) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 1031d11..231828d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,18 +18,11 @@ ApplicationSpecification, EnsureBalanceParameters, ensure_funded, - get_account, - get_algod_client, - get_indexer_client, - get_kmd_client_from_algod_client, replace_template_variables, ) -from legacy_v2_tests import app_client_test if TYPE_CHECKING: - from algosdk.kmd import KMDClient from algosdk.v2client.algod import AlgodClient - from algosdk.v2client.indexer import IndexerClient @pytest.fixture(autouse=True, scope="session") @@ -127,41 +120,6 @@ def is_opted_in(client_fixture: ApplicationClient) -> bool: return any(x for x in apps_local_state if x["id"] == client_fixture.app_id) -@pytest.fixture(scope="session") -def algod_client() -> "AlgodClient": - return get_algod_client() - - -@pytest.fixture(scope="session") -def kmd_client(algod_client: "AlgodClient") -> "KMDClient": - return get_kmd_client_from_algod_client(algod_client) - - -@pytest.fixture(scope="session") -def indexer_client() -> "IndexerClient": - return get_indexer_client() - - -@pytest.fixture -def creator(algod_client: "AlgodClient") -> Account: - creator_name = get_unique_name() - return get_account(algod_client, creator_name) - - -@pytest.fixture(scope="session") -def funded_account(algod_client: "AlgodClient") -> Account: - creator_name = get_unique_name() - return get_account(algod_client, creator_name) - - -@pytest.fixture(scope="session") -def app_spec() -> ApplicationSpecification: - app_spec = app_client_test.app.build() - path = Path(__file__).parent.parent / "legacy_hello_world" / "app_client_test.json" - path.write_text(app_spec.to_json()) - return read_spec("app_client_test.json", deletable=True, updatable=True, template_values={"VERSION": 1}) - - def generate_test_asset(algod_client: "AlgodClient", sender: Account, total: int | None) -> int: if total is None: total = math.floor(random.random() * 100) + 20 diff --git a/tests/test_transaction_composer.py b/tests/test_transaction_composer.py index ac8fd56..a7096c8 100644 --- a/tests/test_transaction_composer.py +++ b/tests/test_transaction_composer.py @@ -27,10 +27,19 @@ @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture diff --git a/tests/transactions/test_transaction_composer.py b/tests/transactions/test_transaction_composer.py index 0d3b75d..9217cc0 100644 --- a/tests/transactions/test_transaction_composer.py +++ b/tests/transactions/test_transaction_composer.py @@ -31,10 +31,19 @@ @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture @@ -82,7 +91,7 @@ def test_add_asset_create(algorand: AlgorandClient, funded_account: Account) -> composer.add_asset_create(params) built = composer.build_transactions() - response = composer.execute(max_rounds_to_wait=20) + response = composer.send(max_rounds_to_wait=20) created_asset = algorand.client.algod.asset_info( algorand.client.algod.pending_transaction_info(response.tx_ids[0])["asset-index"] # type: ignore[call-overload] )["params"] @@ -138,7 +147,7 @@ def test_add_asset_config(algorand: AlgorandClient, funded_account: Account, fun assert txn.index == asset_before_config_index assert txn.manager == funded_secondary_account.address - composer.execute(max_rounds_to_wait=20) + composer.send(max_rounds_to_wait=20) updated_asset = algorand.client.algod.asset_info(asset_id=asset_before_config_index)["params"] # type: ignore[call-overload] assert updated_asset["manager"] == funded_secondary_account.address @@ -165,7 +174,7 @@ def test_add_app_create(algorand: AlgorandClient, funded_account: Account) -> No assert txn.sender == funded_account.address assert txn.approval_program == b"\x06\x81\x01" assert txn.clear_program == b"\x06\x81\x01" - composer.execute(max_rounds_to_wait=20) + composer.send(max_rounds_to_wait=20) def test_add_app_call_method_call(algorand: AlgorandClient, funded_account: Account) -> None: @@ -183,7 +192,7 @@ def test_add_app_call_method_call(algorand: AlgorandClient, funded_account: Acco schema={"global_ints": 0, "global_bytes": 0, "local_ints": 0, "local_bytes": 0}, ) ) - response = composer.execute() + response = composer.send() app_id = algorand.client.algod.pending_transaction_info(response.tx_ids[0])["application-index"] # type: ignore[call-overload] composer = TransactionComposer( @@ -204,8 +213,8 @@ def test_add_app_call_method_call(algorand: AlgorandClient, funded_account: Acco assert isinstance(built.transactions[0], ApplicationCallTxn) txn = built.transactions[0] assert txn.sender == funded_account.address - response = composer.execute(max_rounds_to_wait=20) - assert response.returns[-1] == "Hello, world" + response = composer.send(max_rounds_to_wait=20) + assert response.returns[-1].return_value == "Hello, world" def test_simulate(algorand: AlgorandClient, funded_account: Account) -> None: diff --git a/tests/transactions/test_transaction_creator.py b/tests/transactions/test_transaction_creator.py index cd034db..3c94404 100644 --- a/tests/transactions/test_transaction_creator.py +++ b/tests/transactions/test_transaction_creator.py @@ -35,10 +35,19 @@ @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture diff --git a/tests/transactions/test_transaction_sender.py b/tests/transactions/test_transaction_sender.py index 5c4ea01..dd207bc 100644 --- a/tests/transactions/test_transaction_sender.py +++ b/tests/transactions/test_transaction_sender.py @@ -16,7 +16,6 @@ from algokit_utils import ( Account, - get_account, ) from algokit_utils._legacy_v2.application_specification import ApplicationSpecification from algokit_utils.applications.app_manager import AppManager @@ -39,14 +38,22 @@ TransactionComposer, ) from algokit_utils.transactions.transaction_sender import AlgorandClientTransactionSender -from tests.conftest import get_unique_name @pytest.fixture -def algorand(funded_account: Account) -> AlgorandClient: - client = AlgorandClient.default_local_net() - client.set_signer(sender=funded_account.address, signer=funded_account.signer) - return client +def algorand() -> AlgorandClient: + return AlgorandClient.default_local_net() + + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account @pytest.fixture @@ -55,8 +62,13 @@ def sender(funded_account: Account) -> Account: @pytest.fixture -def receiver(algod_client: "algosdk.v2client.algod.AlgodClient") -> Account: - return get_account(algod_client, get_unique_name()) +def receiver(algorand: AlgorandClient) -> Account: + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, dispenser, AlgoAmount.from_algos(100), min_funding_increment=AlgoAmount.from_algos(1) + ) + return new_account @pytest.fixture @@ -94,20 +106,18 @@ def test_hello_world_arc32_app_id( @pytest.fixture -def transaction_sender( - algod_client: "algosdk.v2client.algod.AlgodClient", sender: Account -) -> AlgorandClientTransactionSender: +def transaction_sender(algorand: AlgorandClient, sender: Account) -> AlgorandClientTransactionSender: def new_group() -> TransactionComposer: return TransactionComposer( - algod=algod_client, + algod=algorand.client.algod, get_signer=lambda _: sender.signer, ) return AlgorandClientTransactionSender( new_group=new_group, - asset_manager=AssetManager(algod_client, new_group), - app_manager=AppManager(algod_client), - algod_client=algod_client, + asset_manager=AssetManager(algorand.client.algod, new_group), + app_manager=AppManager(algorand.client.algod), + algod_client=algorand.client.algod, ) @@ -413,7 +423,8 @@ def test_app_call_method_call( ) result = transaction_sender.app_call_method_call(params) - assert result.return_value == "Hello2, test" + assert result.return_value + assert result.return_value.return_value == "Hello2, test" @patch("logging.Logger.debug")