Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/algokit_utils/errors/logic_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


LOGIC_ERROR = (
".*transaction (?P<transaction_id>[A-Z0-9]+): (logic eval error: )?(?P<message>.*). Details: .*pc=(?P<pc>[0-9]+).*"
".*transaction (?P<transaction_id>[A-Z0-9]+): logic eval error: (?P<message>.*). Details: .*pc=(?P<pc>[0-9]+).*"
)


Expand Down
47 changes: 12 additions & 35 deletions src/algokit_utils/transactions/transaction_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from algosdk.transaction import OnComplete, SuggestedParams
from algosdk.v2client.algod import AlgodClient
from algosdk.v2client.models.simulate_request import SimulateRequest
from typing_extensions import Never, deprecated
from typing_extensions import deprecated

from algokit_utils.applications.abi import ABIReturn, ABIValue
from algokit_utils.applications.app_manager import AppManager
Expand Down Expand Up @@ -667,7 +667,7 @@ def _encode_lease(lease: str | bytes | None) -> bytes | None:
raise TypeError(f"Unknown lease type received of {type(lease)}")


def _get_group_execution_info(
def _get_group_execution_info( # noqa: C901
atc: AtomicTransactionComposer,
algod: AlgodClient,
populate_app_call_resources: bool | None = None,
Expand All @@ -682,7 +682,6 @@ def _get_group_execution_info(
txn_groups=[],
allow_unnamed_resources=True,
allow_empty_signatures=True,
exec_trace_config=algosdk.v2client.models.SimulateTraceConfig(enable=True),
)

# Clone ATC with null signers
Expand Down Expand Up @@ -717,8 +716,16 @@ def _get_group_execution_info(
group_response = result.simulate_response["txn-groups"][0]

if group_response.get("failure-message"):
_handle_simulation_error(
group_response, cover_app_call_inner_transaction_fees=cover_app_call_inner_transaction_fees
msg = group_response["failure-message"]
if cover_app_call_inner_transaction_fees and "fee too small" in msg:
raise ValueError(
"Fees were too small to resolve execution info via simulate. "
"You may need to increase an app call transaction maxFee."
)
failed_at = group_response.get("failed-at", [0])[0]
raise ValueError(
f"Error resolving execution info via simulate in transaction {failed_at}: "
f"{group_response['failure-message']}"
)

# Build execution info
Expand Down Expand Up @@ -756,36 +763,6 @@ def _get_group_execution_info(
)


def _handle_simulation_error(
group_response: dict[str, Any], *, cover_app_call_inner_transaction_fees: bool | None
) -> Never:
msg = group_response["failure-message"]
if cover_app_call_inner_transaction_fees and "fee too small" in msg:
raise ValueError(
"Fees were too small to resolve execution info via simulate. "
"You may need to increase an app call transaction maxFee."
)
failed_at = group_response.get("failed-at", [0])[0]
details = ""
if "logic eval error" not in msg:
# extract last pc from trace so we can format an error that can be parsed into a LogicError
try:
trace = group_response["txn-results"][failed_at]["exec-trace"]
except (KeyError, IndexError):
pass
else:
try:
program_trace = trace["approval-program-trace"]
except KeyError:
program_trace = trace["clear-program-trace"]
pc = program_trace[-1]["pc"]
details = f". Details: pc={pc}"
raise ValueError(
f"Error resolving execution info via simulate in transaction {failed_at}: "
f"{group_response['failure-message']}{details}"
)


def _calculate_required_fee_delta(
txn: transaction.Transaction, txn_result: dict[str, Any], *, per_byte_txn_fee: int, min_txn_fee: int
) -> int:
Expand Down
65 changes: 35 additions & 30 deletions tests/applications/test_app_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import pytest
from algosdk.atomic_transaction_composer import TransactionSigner, TransactionWithSigner

from algokit_utils import SendParams
from algokit_utils._legacy_v2.application_specification import ApplicationSpecification
from algokit_utils.algorand import AlgorandClient
from algokit_utils.applications.abi import ABIType
Expand Down Expand Up @@ -168,15 +167,44 @@ def testing_app_puya_arc32_app_spec() -> ApplicationSpecification:


@pytest.fixture
def test_app_client_puya(
def testing_app_puya_arc32_app_id(
algorand: AlgorandClient, funded_account: SigningAccount, testing_app_puya_arc32_app_spec: ApplicationSpecification
) -> int:
global_schema = testing_app_puya_arc32_app_spec.global_state_schema
local_schema = testing_app_puya_arc32_app_spec.local_state_schema

response = algorand.send.app_create(
AppCreateParams(
sender=funded_account.address,
approval_program=testing_app_puya_arc32_app_spec.approval_program,
clear_state_program=testing_app_puya_arc32_app_spec.clear_program,
schema={
"global_byte_slices": int(global_schema.num_byte_slices) if global_schema.num_byte_slices else 0,
"global_ints": int(global_schema.num_uints) if global_schema.num_uints else 0,
"local_byte_slices": int(local_schema.num_byte_slices) if local_schema.num_byte_slices else 0,
"local_ints": int(local_schema.num_uints) if local_schema.num_uints else 0,
},
)
)
return response.app_id


@pytest.fixture
def test_app_client_puya(
algorand: AlgorandClient,
funded_account: SigningAccount,
testing_app_puya_arc32_app_spec: ApplicationSpecification,
testing_app_puya_arc32_app_id: int,
) -> AppClient:
factory = algorand.client.get_app_factory(
app_spec=testing_app_puya_arc32_app_spec,
default_sender=funded_account.address,
return AppClient(
AppClientParams(
default_sender=funded_account.address,
default_signer=funded_account.signer,
app_id=testing_app_puya_arc32_app_id,
algorand=algorand,
app_spec=testing_app_puya_arc32_app_spec,
)
)
app_client, _ = factory.send.bare.create()
return app_client


def test_clone_overriding_default_sender_and_inheriting_app_name(
Expand Down Expand Up @@ -681,29 +709,6 @@ def test_box_methods_with_arc4_returns_parametrized(
assert abi_decoded_boxes[0].value == arg_value


@pytest.mark.parametrize(
"populate",
[
True,
# False, # enable this test once rejected transactions contain pc information
],
)
def test_txn_with_reject(test_app_client_puya: AppClient, *, populate: bool) -> None:
with pytest.raises(LogicError, match="expect this txn to be rejected"):
test_app_client_puya.send.call(
AppClientMethodCallParams(method="rejected"), send_params=SendParams(populate_app_call_resources=populate)
)


@pytest.mark.parametrize("populate", [True, False])
def test_txn_with_logic_err(test_app_client_puya: AppClient, *, populate: bool) -> None:
with pytest.raises(LogicError, match="expect this to be a logic err") as exc:
test_app_client_puya.send.call(
AppClientMethodCallParams(method="logic_err"), send_params=SendParams(populate_app_call_resources=populate)
)
assert exc


def test_abi_with_default_arg_method(
algorand: AlgorandClient,
funded_account: SigningAccount,
Expand Down
Loading
Loading