Skip to content
Open
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
28 changes: 23 additions & 5 deletions pycardano/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ class TransactionBuilder:
init=False, default_factory=lambda: []
)

_reference_script_inputs: Set[TransactionInput] = field(
init=False, default_factory=lambda: set()
)

_should_estimate_execution_units: Optional[bool] = field(init=False, default=None)

def add_input(self, utxo: UTxO) -> TransactionBuilder:
Expand All @@ -219,9 +223,23 @@ def add_input(self, utxo: UTxO) -> TransactionBuilder:
"""
self.inputs.append(utxo)
if utxo.output.script:
self._reference_scripts.append(utxo.output.script)
self._add_reference_script(utxo)
return self

def _add_reference_script(self, utxo: UTxO):
"""Count ``utxo``'s script toward the reference script fee.

The ledger charges the scripts held by a transaction's inputs and reference
inputs once per UTxO, however many redeemers resolve their script from it, so
a UTxO that is already counted is skipped. The same script held by two
different UTxOs still counts twice.
"""
assert utxo.output.script is not None
if utxo.input in self._reference_script_inputs:
return
self._reference_script_inputs.add(utxo.input)
self._reference_scripts.append(utxo.output.script)

def _consolidate_redeemer(self, redeemer):
if self._should_estimate_execution_units is None:
if redeemer.ex_units:
Expand Down Expand Up @@ -342,7 +360,7 @@ def add_script_input(

if candidate_utxo is not None and candidate_utxo != utxo:
self.reference_inputs.add(candidate_utxo)
self._reference_scripts.append(candidate_script)
self._add_reference_script(candidate_utxo)
break
if not found_valid_script:
raise InvalidArgumentException(
Expand Down Expand Up @@ -380,7 +398,7 @@ def add_minting_script(
assert script.output.script is not None
self._minting_script_to_redeemers.append((script.output.script, redeemer))
self.reference_inputs.add(script)
self._reference_scripts.append(script.output.script)
self._add_reference_script(script)
else:
self._minting_script_to_redeemers.append((script, redeemer))
return self
Expand Down Expand Up @@ -414,7 +432,7 @@ def add_withdrawal_script(
(script.output.script, redeemer)
)
self.reference_inputs.add(script)
self._reference_scripts.append(script.output.script)
self._add_reference_script(script)
else:
self._withdrawal_script_to_redeemers.append((script, redeemer))
return self
Expand Down Expand Up @@ -455,7 +473,7 @@ def add_certificate_script(
(script.output.script, redeemer)
)
self.reference_inputs.add(script)
self._reference_scripts.append(script.output.script)
self._add_reference_script(script)
else:
self._certificate_script_to_redeemers.append((script, redeemer))
return self
Expand Down
74 changes: 74 additions & 0 deletions test/pycardano/test_txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2932,6 +2932,80 @@ def test_add_withdrawal_script_from_utxo(chain_context):
assert existing_script_utxo.input in tx_body.reference_inputs


def test_reference_script_shared_by_spend_and_withdrawal_counted_once(chain_context):
"""A reference script UTxO used by several redeemers is a single reference input,
so the ledger charges its size once toward the reference script fee."""
tx_builder = TransactionBuilder(chain_context)
plutus_script = PlutusV2Script(b"dummy shared script")
script_hash = plutus_script_hash(plutus_script)
datum = PlutusData()
script_utxo = UTxO(
TransactionInput.from_primitive(
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
),
TransactionOutput(Address(script_hash), 10000000, datum_hash=datum.hash()),
)
reference_utxo = UTxO(
TransactionInput.from_primitive(
["41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7", 1]
),
TransactionOutput(Address(script_hash), 1234567, script=plutus_script),
)
tx_builder.add_script_input(
script_utxo,
script=reference_utxo,
datum=datum,
redeemer=Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000)),
)
stake_address = Address(
payment_part=None, staking_part=script_hash, network=chain_context.network
)
tx_builder.withdrawals = Withdrawals({bytes(stake_address): 0})
tx_builder.add_withdrawal_script(
reference_utxo, Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
)

assert tx_builder.reference_inputs == {reference_utxo}
assert tx_builder._ref_script_size() == len(plutus_script)


def test_same_script_in_two_reference_utxos_counted_per_utxo(chain_context):
"""The ledger charges every reference input, so one script held by two reference
UTxOs counts twice."""
tx_builder = TransactionBuilder(chain_context)
plutus_script = PlutusV2Script(b"dummy shared script")
script_hash = plutus_script_hash(plutus_script)
datum = PlutusData()
for index in (0, 1):
tx_builder.add_script_input(
UTxO(
TransactionInput.from_primitive(
[
"18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef",
index,
]
),
TransactionOutput(
Address(script_hash), 10000000, datum_hash=datum.hash()
),
),
script=UTxO(
TransactionInput.from_primitive(
[
"41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7",
index,
]
),
TransactionOutput(Address(script_hash), 1234567, script=plutus_script),
),
datum=datum,
redeemer=Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000)),
)

assert len(tx_builder.reference_inputs) == 2
assert tx_builder._ref_script_size() == 2 * len(plutus_script)


def test_withdrawal_script_wrong_redeemer_tag(chain_context):
"""Test that withdrawal script with wrong redeemer tag raises exception."""
plutus_script = PlutusV2Script(b"dummy withdrawal script")
Expand Down
Loading