From dfea9e0cfe88a7b84300a8dd81c480fd454fdb45 Mon Sep 17 00:00:00 2001 From: Daniel McGregor Date: Mon, 13 Mar 2023 10:07:39 +0800 Subject: [PATCH] feat: return ApplicationClient when deploying app --- src/algokit_utils/app.py | 31 +++++++++----- src/algokit_utils/application_client.py | 57 ++++++++----------------- tests/test_deploy_scenarios.py | 37 +++++++--------- 3 files changed, 52 insertions(+), 73 deletions(-) diff --git a/src/algokit_utils/app.py b/src/algokit_utils/app.py index cd852ca..9d98105 100644 --- a/src/algokit_utils/app.py +++ b/src/algokit_utils/app.py @@ -232,6 +232,13 @@ class OnSchemaBreak(Enum): DeleteApp = 2 +def _indexer_wait_for_round(indexer_client: IndexerClient, round_target: int, max_attempts: int = 100) -> None: + for _attempts in range(max_attempts): + health = indexer_client.health() # type: ignore[no-untyped-call] + if health["round"] >= round_target: + break + + # TODO: split this function up def deploy_app( algod_client: AlgodClient, @@ -245,9 +252,7 @@ def deploy_app( allow_update: bool | None = None, allow_delete: bool | None = None, template_values: dict[str, int | str] | None = None, -) -> App: - # TODO: return ApplicationClient - +) -> ApplicationClient: # make a copy app_spec = ApplicationSpecification.from_json(app_spec.to_json()) @@ -287,10 +292,11 @@ def deploy_app( algod_client, app_spec, app_id=app_id, signer=AccountTransactionSigner(creator_account.private_key) ) - def create_app() -> App: + def create_app() -> ApplicationClient: create_result = app_client.create(note=app_spec_note.encode()) - logger.info(f"{name} ({version}) deployed successfully, with app id {create_result.app_id}.") - return App(create_result.app_id, create_result.app_address, create_result.confirmed_round, app_spec_note) + logger.info(f"{name} ({version}) deployed successfully, with app id {app_client.app_id}.") + _indexer_wait_for_round(indexer_client, create_result.confirmed_round) + return app_client if app is None: logger.info(f"{name} not found in {creator_account.address} account, deploying app.") @@ -322,7 +328,7 @@ def create_app() -> App: current_local_schema, required_local_schema ) - def create_and_delete_app() -> App: + def create_and_delete_app() -> ApplicationClient: logger.info(f"Deploying {name} ({version}) in {creator_account.address} account.") new_app = create_app() @@ -335,17 +341,20 @@ def create_and_delete_app() -> App: old_app_client = ApplicationClient( algod_client, app_spec, app_id=app.id, signer=AccountTransactionSigner(creator_account.private_key) ) - old_app_client.delete() + delete_result = old_app_client.delete() + + _indexer_wait_for_round(indexer_client, delete_result.confirmed_round) return new_app - def update_app() -> App: + def update_app() -> ApplicationClient: assert on_update == OnUpdate.UpdateApp assert app logger.info(f"Updating {name} to {version} in {creator_account.address} account, with app id {app.id}") update_result = app_client.update(note=app_spec_note.encode()) - return App(update_result.app_id, update_result.app_address, update_result.confirmed_round, app_spec_note) + _indexer_wait_for_round(indexer_client, update_result.confirmed_round) + return app_client if schema_breaking_change: logger.warning( @@ -400,4 +409,4 @@ def update_app() -> App: logger.info("No detected changes in app, nothing to do.") - return app + return app_client diff --git a/src/algokit_utils/application_client.py b/src/algokit_utils/application_client.py index 125a5ed..732786e 100644 --- a/src/algokit_utils/application_client.py +++ b/src/algokit_utils/application_client.py @@ -59,14 +59,6 @@ def num_extra_program_pages(approval: bytes, clear: bytes) -> int: return ceil(((len(approval) + len(clear)) - APP_PAGE_MAX_SIZE) / APP_PAGE_MAX_SIZE) -@dataclasses.dataclass -class CreateUpdateResult: - app_id: int - app_address: str - transaction_id: str - confirmed_round: int - - class ApplicationClient: def __init__( self, @@ -126,7 +118,7 @@ def create( on_complete: transaction.OnComplete = transaction.OnComplete.NoOpOC, extra_pages: int | None = None, **kwargs: Any, - ) -> CreateUpdateResult: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with application id == 0 and the schema and source from the Application passed""" @@ -173,13 +165,11 @@ def create( create_txid = create_result.tx_ids[0] result = self.client.pending_transaction_info(create_txid) - app_id = result["application-index"] - app_addr = get_application_address(app_id) - self.app_id = app_id - self.app_addr = app_addr + self.app_id = result["application-index"] + self.app_addr = get_application_address(self.app_id) - return CreateUpdateResult(app_id, app_addr, create_txid, create_result.confirmed_round) + return create_result def update( self, @@ -187,7 +177,7 @@ def update( signer: TransactionSigner | None = None, suggested_params: transaction.SuggestedParams | None = None, **kwargs: Any, - ) -> CreateUpdateResult: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with OnComplete set to UpdateApplication and source from the Application passed""" @@ -221,11 +211,7 @@ def update( ) ) - update_result = self._execute_atc(atc) - - return CreateUpdateResult( - self.app_id, get_application_address(self.app_id), update_result.tx_ids[0], update_result.confirmed_round - ) + return self._execute_atc(atc) def opt_in( self, @@ -233,7 +219,7 @@ def opt_in( signer: TransactionSigner | None = None, suggested_params: transaction.SuggestedParams | None = None, **kwargs: Any, - ) -> str: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with OnComplete set to OptIn""" sp = self.get_suggested_params(suggested_params) @@ -263,9 +249,7 @@ def opt_in( ) ) - opt_in_result = self._execute_atc(atc) - - return opt_in_result.tx_ids[0] + return self._execute_atc(atc) def close_out( self, @@ -273,7 +257,7 @@ def close_out( signer: TransactionSigner | None = None, suggested_params: transaction.SuggestedParams | None = None, **kwargs: Any, - ) -> str: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with OnComplete set to CloseOut""" sp = self.get_suggested_params(suggested_params) @@ -303,9 +287,7 @@ def close_out( ) ) - close_out_result = self._execute_atc(atc) - - return close_out_result.tx_ids[0] + return self._execute_atc(atc) def clear_state( self, @@ -313,7 +295,7 @@ def clear_state( signer: TransactionSigner | None = None, suggested_params: transaction.SuggestedParams | None = None, **kwargs: Any, - ) -> str: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with OnComplete set to ClearState""" sp = self.get_suggested_params(suggested_params) @@ -332,9 +314,7 @@ def clear_state( ) ) - clear_state_result = atc.execute(self.client, 4) - - return clear_state_result.tx_ids[0] + return atc.execute(self.client, 4) def delete( self, @@ -342,7 +322,7 @@ def delete( signer: TransactionSigner | None = None, suggested_params: transaction.SuggestedParams | None = None, **kwargs: Any, - ) -> str: + ) -> AtomicTransactionResponse: """Submits a signed ApplicationCallTransaction with OnComplete set to DeleteApplication""" sp = self.get_suggested_params(suggested_params) @@ -372,9 +352,7 @@ def delete( ) ) - delete_result = self._execute_atc(atc) - - return delete_result.tx_ids[0] + return self._execute_atc(atc) def prepare( self, @@ -563,12 +541,12 @@ def add_transaction( atc.add_transaction(TransactionWithSigner(txn=txn, signer=self.signer)) return atc - def fund(self, amt: int, addr: str | None = None) -> str: + def fund(self, amt: int, addr: str | None = None) -> AtomicTransactionResponse: """convenience method to pay the address passed, defaults to paying the app address for this client from the current signer""" signer, sender = self._resolve_signer_sender(self.signer, self.sender) - sp = self.client.suggested_params() + sp = self.get_suggested_params() rcv = self.app_addr if addr is None else addr @@ -579,8 +557,7 @@ def fund(self, amt: int, addr: str | None = None) -> str: signer=signer, ) ) - atc.execute(self.client, 4) - return atc.tx_ids.pop() + return atc.execute(self.client, 4) def get_global_state(self, *, raw: bool = False) -> dict[bytes | str, bytes | str | int]: """gets the global state info for the app id set""" diff --git a/tests/test_deploy_scenarios.py b/tests/test_deploy_scenarios.py index d081b47..51cfd6c 100644 --- a/tests/test_deploy_scenarios.py +++ b/tests/test_deploy_scenarios.py @@ -5,11 +5,11 @@ from uuid import uuid4 import pytest +from algokit_utils import ApplicationClient from algokit_utils.account import get_account, get_sandbox_default_account from algokit_utils.app import ( DELETABLE_TEMPLATE_NAME, UPDATABLE_TEMPLATE_NAME, - App, DeploymentFailedError, OnSchemaBreak, OnUpdate, @@ -44,7 +44,7 @@ def deploy( on_schema_break: OnSchemaBreak = OnSchemaBreak.Fail, allow_delete: bool | None = None, allow_update: bool | None = None, - ) -> App: + ) -> ApplicationClient: app = deploy_app( self.algod_client, self.indexer, @@ -56,8 +56,7 @@ def deploy( allow_update=allow_update, allow_delete=allow_delete, ) - self._wait_for_round(app.created_at_round) - self.app_ids.append(app.id) + self.app_ids.append(app.app_id) return app def check_log_stability(self, suffix: str = ""): @@ -76,12 +75,6 @@ def _normalize_logs(self, logs: str) -> str: logs = re.sub(r"app id \d+", r"{appN_failed}", logs) return logs - def _wait_for_round(self, round_target: int, max_attempts: int = 100) -> None: - for _attempts in range(max_attempts): - health = self.indexer.health() - if health["round"] >= round_target: - break - @pytest.fixture def deploy_fixture(caplog: pytest.LogCaptureFixture, request: pytest.FixtureRequest) -> DeployFixture: @@ -129,7 +122,7 @@ def test_deploy_app_with_no_existing_app_succeeds(deploy_fixture: DeployFixture) app = deploy_fixture.deploy(v1, version="1.0") - assert app.id + assert app.app_id deploy_fixture.check_log_stability() @@ -137,11 +130,11 @@ def test_deploy_app_with_existing_updatable_app_succeeds(deploy_fixture: DeployF v1, v2, _ = get_specs(updatable=True) app_v1 = deploy_fixture.deploy(v1, version="1.0") - assert app_v1.id + assert app_v1.app_id app_v2 = deploy_fixture.deploy(v2, version="2.0") - assert app_v1.id == app_v2.id + assert app_v1.app_id == app_v2.app_id deploy_fixture.check_log_stability() @@ -149,7 +142,7 @@ def test_deploy_app_with_existing_immutable_app_fails(deploy_fixture: DeployFixt v1, v2, _ = get_specs(updatable=False) app_v1 = deploy_fixture.deploy(v1, version="1.0") - assert app_v1.id + assert app_v1.app_id with pytest.raises(LogicException) as error: deploy_fixture.deploy(v2, version="2.0") @@ -164,11 +157,11 @@ def test_deploy_app_with_existing_immutable_app_and_on_update_equals_delete_app_ v1, v2, _ = get_specs(updatable=False, deletable=True) app_v1 = deploy_fixture.deploy(v1, version="1.0") - assert app_v1.id + assert app_v1.app_id app_v2 = deploy_fixture.deploy(v2, version="2.0", on_update=OnUpdate.DeleteApp) - assert app_v1.id != app_v2.id + assert app_v1.app_id != app_v2.app_id deploy_fixture.check_log_stability() @@ -176,10 +169,10 @@ def test_deploy_app_with_existing_deletable_app_succeeds(deploy_fixture: DeployF v1, v2, _ = get_specs(deletable=True) app_v1 = deploy_fixture.deploy(v1, version="1.0") - assert app_v1.id + assert app_v1.app_id app_v2 = deploy_fixture.deploy(v2, version="2.0", on_update=OnUpdate.DeleteApp) - assert app_v1.id != app_v2.id + assert app_v1.app_id != app_v2.app_id deploy_fixture.check_log_stability() @@ -187,7 +180,7 @@ def test_deploy_app_with_existing_permanent_app_fails(deploy_fixture: DeployFixt v1, _, v3 = get_specs(deletable=False) app_v1 = deploy_fixture.deploy(v1, version="1.0") - assert app_v1.id + assert app_v1.app_id with pytest.raises(DeploymentFailedError) as error: deploy_fixture.deploy(v3, version="3.0") @@ -201,7 +194,7 @@ def test_deploy_app_with_existing_permanent_app_and_on_schema_break_equals_delet v1, _, v3 = get_specs(deletable=False) app_v1 = deploy_fixture.deploy(v1, "1.0") - assert app_v1.id + assert app_v1.app_id with pytest.raises(LogicException) as exc_info: deploy_fixture.deploy(v3, "3.0", on_schema_break=OnSchemaBreak.DeleteApp) @@ -281,7 +274,7 @@ def test_deploy_with_schema_breaking_change( app_v1 = deploy_fixture.deploy( v1, "1.0", allow_delete=deletable == deletable.Yes, allow_update=updatable == updatable.Yes ) - assert app_v1.id + assert app_v1.app_id try: deploy_fixture.deploy( @@ -315,7 +308,7 @@ def test_deploy_with_update( app_v1 = deploy_fixture.deploy( v1, "1.0", allow_delete=deletable == deletable.Yes, allow_update=updatable == updatable.Yes ) - assert app_v1.id + assert app_v1.app_id try: deploy_fixture.deploy(