Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip collateral return if it is too small #411

Merged
merged 2 commits into from
Jan 4, 2025
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
25 changes: 24 additions & 1 deletion pycardano/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
63 changes: 63 additions & 0 deletions test/pycardano/test_txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading