@@ -623,84 +623,74 @@ def test_handles_expensive_abi_calls_with_ensure_budget(self) -> None:
623
623
assert len (result .confirmation .get ("inner-txns" , [])) == 9 # type: ignore[union-attr]
624
624
self ._assert_min_fee (self .app_client1 , params , expected_fee )
625
625
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"""
628
630
629
631
params = AppClientMethodCallParams (
630
632
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 }
633
637
)
634
- result = self .app_client1 .send .call (params , send_params = {"cover_app_call_inner_transaction_fees" : True })
635
638
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
638
640
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
647
658
result = self .app_client1 .send .call (
648
659
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 ),
651
663
),
652
664
send_params = {
653
665
"cover_app_call_inner_transaction_fees" : True ,
654
666
},
655
667
)
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"""
661
668
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
676
672
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"""
680
675
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
683
681
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
686
682
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 ),
697
686
)
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
+ )
704
694
705
695
def _assert_min_fee (self , app_client : AppClient , params : AppClientMethodCallParams , fee : int ) -> None :
706
696
"""Helper to assert minimum required fee"""
0 commit comments