Skip to content

Commit

Permalink
feat: return ApplicationClient when deploying app
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-makerx committed Mar 22, 2023
1 parent 82d0218 commit dfea9e0
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 73 deletions.
31 changes: 20 additions & 11 deletions src/algokit_utils/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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())

Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand Down Expand Up @@ -400,4 +409,4 @@ def update_app() -> App:

logger.info("No detected changes in app, nothing to do.")

return app
return app_client
57 changes: 17 additions & 40 deletions src/algokit_utils/application_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -173,21 +165,19 @@ 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,
sender: str | None = None,
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"""

Expand Down Expand Up @@ -221,19 +211,15 @@ 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,
sender: str | None = None,
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)
Expand Down Expand Up @@ -263,17 +249,15 @@ 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,
sender: str | None = None,
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)
Expand Down Expand Up @@ -303,17 +287,15 @@ 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,
sender: str | None = None,
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)
Expand All @@ -332,17 +314,15 @@ 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,
sender: str | None = None,
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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand All @@ -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"""
Expand Down
37 changes: 15 additions & 22 deletions tests/test_deploy_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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 = ""):
Expand All @@ -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:
Expand Down Expand Up @@ -129,27 +122,27 @@ 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()


def test_deploy_app_with_existing_updatable_app_succeeds(deploy_fixture: DeployFixture):
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()


def test_deploy_app_with_existing_immutable_app_fails(deploy_fixture: DeployFixture):
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")
Expand All @@ -164,30 +157,30 @@ 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()


def test_deploy_app_with_existing_deletable_app_succeeds(deploy_fixture: DeployFixture):
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()


def test_deploy_app_with_existing_permanent_app_fails(deploy_fixture: DeployFixture):
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")
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit dfea9e0

Please sign in to comment.