Skip to content

Commit

Permalink
Skip collateral return if it is too small (#411)
Browse files Browse the repository at this point in the history
* Skip collateral return if it is too small

* Minor fix
  • Loading branch information
cffls authored Jan 4, 2025
1 parent d7d2053 commit 6078263
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
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

0 comments on commit 6078263

Please sign in to comment.