From cad515075883e77ff6b4b0adc81ff925d68610c5 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 22 Dec 2024 11:17:02 -0800 Subject: [PATCH 1/2] Skip collateral return if it is too small --- pycardano/txbuilder.py | 25 ++++++++++++- test/pycardano/test_txbuilder.py | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 00e25b76..525c5463 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -144,6 +144,10 @@ class TransactionBuilder: _collateral_return: Optional[TransactionOutput] = field(init=False, default=None) + collateral_return_threshold: int = 1_000_000 + """The minimum amount of lovelace above which + the remaining collateral (total_collateral_amount - actually_used_amount) will be returned.""" + _total_collateral: Optional[int] = field(init=False, default=None) _inputs_to_redeemers: Dict[UTxO, Redeemer] = field( @@ -1336,6 +1340,20 @@ def build( return tx_body + def _should_add_collateral_return(self, collateral_return: Value) -> bool: + """Check if it is necessary to add a collateral return output. + + Args: + collateral_return (Value): The potential collateral return amount. + + Returns: + bool: True if a collateral return output should be added, False otherwise. + """ + return ( + collateral_return.coin > max(self.collateral_return_threshold, 1_000_000) + or collateral_return.multi_asset.count(lambda p, n, v: v > 0) > 0 + ) + def _set_collateral_return(self, collateral_return_address: Optional[Address]): """Calculate and set the change returned from the collateral inputs. @@ -1370,7 +1388,8 @@ def _add_collateral_input(cur_total, candidate_inputs): while ( cur_total.coin < collateral_amount - or 0 + or self._should_add_collateral_return(cur_collateral_return) + and 0 <= cur_collateral_return.coin < min_lovelace_post_alonzo( TransactionOutput( @@ -1422,6 +1441,10 @@ def _add_collateral_input(cur_total, candidate_inputs): ) else: return_amount = total_input - collateral_amount + + if not self._should_add_collateral_return(return_amount): + return # No need to return collateral if the remaining amount is too small + min_lovelace_val = min_lovelace_post_alonzo( TransactionOutput(collateral_return_address, return_amount), self.context, diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 93e3c554..78732bba 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1048,6 +1048,69 @@ def test_collateral_return(chain_context): ) +@pytest.mark.parametrize( + "collateral_amount, collateral_return_threshold, has_return", + [ + (Value(4_000_000), 0, False), + (Value(4_000_000), 1_000_000, False), + (Value(6_000_000), 2_000_000, True), + (Value(6_000_000), 3_000_000, False), + ( + Value( + 6_000_000, + MultiAsset.from_primitive({b"1" * 28: {b"Token1": 1, b"Token2": 2}}), + ), + 3_000_000, + True, + ), + ], +) +def test_no_collateral_return( + chain_context, collateral_amount, collateral_return_threshold, has_return +): + original_utxos = chain_context.utxos( + "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + ) + with patch.object(chain_context, "utxos") as mock_utxos: + tx_builder = TransactionBuilder( + chain_context, collateral_return_threshold=collateral_return_threshold + ) + tx_in1 = TransactionInput.from_primitive( + ["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0] + ) + plutus_script = PlutusV1Script(b"dummy test script") + script_hash = plutus_script_hash(plutus_script) + script_address = Address(script_hash) + datum = PlutusData() + utxo1 = UTxO( + tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash()) + ) + + existing_script_utxo = UTxO( + TransactionInput.from_primitive( + [ + "41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7", + 1, + ] + ), + TransactionOutput(script_address, 1234567, script=plutus_script), + ) + + original_utxos[0].output.amount = collateral_amount + + mock_utxos.return_value = original_utxos[:1] + [existing_script_utxo] + + redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000)) + tx_builder.add_script_input(utxo1, datum=datum, redeemer=redeemer) + receiver = Address.from_primitive( + "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + ) + tx_builder.add_output(TransactionOutput(receiver, 5000000)) + tx_body = tx_builder.build(change_address=receiver) + assert (tx_body.collateral_return is not None) == has_return + assert (tx_body.total_collateral is not None) == has_return + + def test_collateral_return_min_return_amount(chain_context): original_utxos = chain_context.utxos( "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" From 3327496fb4145fdc73328f59e7fa6c2aaaa615ed Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 22 Dec 2024 16:33:00 -0800 Subject: [PATCH 2/2] Minor fix --- pycardano/txbuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 525c5463..685c70cc 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -145,7 +145,7 @@ class TransactionBuilder: _collateral_return: Optional[TransactionOutput] = field(init=False, default=None) collateral_return_threshold: int = 1_000_000 - """The minimum amount of lovelace above which + """The minimum amount of lovelace above which the remaining collateral (total_collateral_amount - actually_used_amount) will be returned.""" _total_collateral: Optional[int] = field(init=False, default=None)