Skip to content

Commit 360e66c

Browse files
committed
fix: adjust test and clarify some docs
1 parent a1fc23d commit 360e66c

File tree

2 files changed

+50
-60
lines changed

2 files changed

+50
-60
lines changed

tests/transactions/test_fee_coverage.py

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -623,84 +623,74 @@ def test_handles_expensive_abi_calls_with_ensure_budget(self) -> None:
623623
assert len(result.confirmation.get("inner-txns", [])) == 9 # type: ignore[union-attr]
624624
self._assert_min_fee(self.app_client1, params, expected_fee)
625625

626-
def test_readonly_uses_fixed_opcode_budget_without_inner_transactions(self) -> None:
627-
"""Test that readonly calls use fixed opcode budget and don't require inner transactions for opcode top-ups"""
626+
@pytest.mark.parametrize("cover_inner_fees", [True, False])
627+
def test_readonly_uses_fixed_opcode_budget_without_op_up_inner_transactions(self, cover_inner_fees: bool) -> None: # noqa: FBT001
628+
"""Test that readonly calls use fixed opcode budget and don't require inner transactions for op-ups
629+
regardless of fee coverage setting"""
628630

629631
params = AppClientMethodCallParams(
630632
method="burn_ops_readonly",
631-
args=[6200], # This would normally require opcode budget top-ups via inner transactions
632-
max_fee=AlgoAmount.from_micro_algo(12_000),
633+
args=[6200], # This would normally require op-ups via inner transactions
634+
)
635+
result = self.app_client1.send.call(
636+
params, send_params={"cover_app_call_inner_transaction_fees": cover_inner_fees}
633637
)
634-
result = self.app_client1.send.call(params, send_params={"cover_app_call_inner_transaction_fees": True})
635638

636-
# Readonly calls should succeed without creating inner transactions
637-
# because they use the fixed MAX_SIMULATE_OPCODE_BUDGET
639+
# No op-up inner transactions needed regardless of fee coverage setting
638640
assert len(result.confirmation.get("inner-txns", [])) == 0 # type: ignore[union-attr]
639-
assert result.transaction.raw.fee == 12_000
640-
641-
# Verify the call succeeded (void methods return None which is expected)
642-
assert result.tx_ids # Ensure transaction was processed
643-
644-
def test_readonly_works_without_max_fee(self) -> None:
645-
"""Test that readonly calls work without max_fee when cover_app_call_inner_transaction_fees is enabled"""
646-
# Since we provide max opcode budget, readonly calls no longer require max_fee
641+
assert result.transaction.raw.fee == 1_000
642+
assert len(result.tx_ids) == 1
643+
644+
def test_readonly_alters_fee_handling_inner_transactions(self) -> None:
645+
"""Test that inner transaction can be covered using the max_fee"""
646+
# Force `send_inners_with_fees` to be marked as readonly
647+
for method in self.app_client1._app_spec.methods: # noqa: SLF001
648+
if method.name == "send_inners_with_fees":
649+
method.readonly = True
650+
break
651+
652+
# The expected_fee differs to non readonly method call,as we don't want to
653+
# run simulate twice (once for resolving the minimum fee and once for the actual transaction result).
654+
# Because no fees are actually paid with readonly calls,
655+
# we simply use the maxFee value (if set) and skip any minimum fee calculations.
656+
# If this method is running in a non readonly context, the minimum fee would be calculated as 5300n.
657+
expected_fee = 12_000
647658
result = self.app_client1.send.call(
648659
AppClientMethodCallParams(
649-
method="burn_ops_readonly",
650-
args=[6200],
660+
method="send_inners_with_fees",
661+
args=[self.app_client2.app_id, self.app_client3.app_id, [1000, 0, 200, 0, [500, 0]]],
662+
max_fee=AlgoAmount.from_micro_algo(expected_fee),
651663
),
652664
send_params={
653665
"cover_app_call_inner_transaction_fees": True,
654666
},
655667
)
656-
# Verify the call succeeded (void methods return None which is expected)
657-
assert result.tx_ids # Ensure transaction was processed
658-
659-
def test_readonly_works_without_fee_coverage_due_to_fixed_budget(self) -> None:
660-
"""Test that readonly calls work without fee coverage because they use fixed opcode budget"""
661668

662-
# This test verifies that readonly calls don't need inner transaction fee coverage
663-
# because they use MAX_SIMULATE_OPCODE_BUDGET instead of calculating fees dynamically
664-
params = AppClientMethodCallParams(
665-
method="burn_ops_readonly",
666-
args=[6200], # Expensive operation that would normally need fee coverage
667-
max_fee=AlgoAmount.from_micro_algo(7000),
668-
)
669-
670-
result = self.app_client1.send.call(
671-
params,
672-
send_params={
673-
"cover_app_call_inner_transaction_fees": False, # No fee coverage needed
674-
},
675-
)
669+
assert result.transaction.raw.fee == expected_fee
670+
assert len(result.confirmation.get("inner-txns", [])) == 4 # type: ignore[union-attr]
671+
assert len(result.tx_ids) == 1
676672

677-
# Should succeed because fixed budget eliminates need for inner fee calculations
678-
assert result.tx_ids # Ensure transaction was processed
679-
assert len(result.confirmation.get("inner-txns", [])) == 0 # type: ignore[union-attr]
673+
def test_readonly_throws_when_max_fee_too_small(self) -> None:
674+
"""Test that error is thrown when readonly method call max fee is too small to cover inner transaction fees"""
680675

681-
def test_readonly_succeeds_with_low_max_fee_due_to_fixed_budget(self) -> None:
682-
"""Test that readonly calls succeed even with low max_fee because they use fixed opcode budget"""
676+
# Force `send_inners_with_fees` to be marked as readonly
677+
for method in self.app_client1._app_spec.methods: # noqa: SLF001
678+
if method.name == "send_inners_with_fees":
679+
method.readonly = True
680+
break
683681

684-
# This test demonstrates that readonly calls don't fail due to insufficient max_fee
685-
# because the fixed opcode budget eliminates the need for fee-based opcode calculations
686682
params = AppClientMethodCallParams(
687-
method="burn_ops_readonly",
688-
args=[6200], # Expensive operation
689-
max_fee=AlgoAmount.from_micro_algo(2000), # Intentionally low max_fee
690-
)
691-
692-
result = self.app_client1.send.call(
693-
params,
694-
send_params={
695-
"cover_app_call_inner_transaction_fees": True,
696-
},
683+
method="send_inners_with_fees",
684+
args=[self.app_client2.app_id, self.app_client3.app_id, [1000, 0, 200, 0, [500, 0]]],
685+
max_fee=AlgoAmount.from_micro_algo(2000),
697686
)
698-
699-
# Should succeed despite low max_fee because fixed budget prevents fee-based failures
700-
assert result.tx_ids # Ensure transaction was processed
701-
assert len(result.confirmation.get("inner-txns", [])) == 0 # type: ignore[union-attr]
702-
# The transaction fee should be the low max_fee we set, not a calculated higher fee
703-
assert result.transaction.raw.fee == 2000
687+
with pytest.raises(ValueError, match="Fees were too small. You may need to increase the transaction `maxFee`."):
688+
self.app_client1.send.call(
689+
params,
690+
send_params={
691+
"cover_app_call_inner_transaction_fees": True,
692+
},
693+
)
704694

705695
def _assert_min_fee(self, app_client: AppClient, params: AppClientMethodCallParams, fee: int) -> None:
706696
"""Helper to assert minimum required fee"""

tests/transactions/test_transaction_composer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def _get_test_transaction(
319319

320320

321321
def test_transaction_is_capped_by_low_min_txn_fee(algorand: AlgorandClient, funded_account: SigningAccount) -> None:
322-
with pytest.raises(ValueError, match="Transaction fee 1000 is greater than max_fee 1 µALGO"):
322+
with pytest.raises(ValueError, match="Transaction fee 1000 µALGO is greater than maxFee 1 µALGO"):
323323
algorand.send.payment(
324324
PaymentParams(**_get_test_transaction(funded_account), max_fee=AlgoAmount.from_micro_algo(1))
325325
)

0 commit comments

Comments
 (0)