From d5fd4eb12bfdebace4c1ed678ec64f029f797a2f Mon Sep 17 00:00:00 2001 From: Jerry Date: Sat, 20 Sep 2025 18:52:05 -0700 Subject: [PATCH 1/2] [bugfix] spend utxo with script in the output --- integration-test/test/test_plutus.py | 39 ++++++++++++++++++++++++++++ pycardano/txbuilder.py | 8 +++++- test/pycardano/test_txbuilder.py | 6 ----- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/integration-test/test/test_plutus.py b/integration-test/test/test_plutus.py index 6fdbe27b..523a9d1d 100644 --- a/integration-test/test/test_plutus.py +++ b/integration-test/test/test_plutus.py @@ -330,6 +330,45 @@ def test_plutus_v2_ref_script(self): self.assert_output(taker_address, take_output) + @retry(tries=TEST_RETRIES, backoff=1.3, delay=2, jitter=(0, 10)) + @pytest.mark.post_alonzo + def test_plutus_v2_spend_ref_script(self): + # ----------- Create a reference script --------------- + with open("./plutus_scripts/fortytwoV2.plutus", "r") as f: + script_hex = f.read() + forty_two_script = PlutusV2Script(cbor2.loads(bytes.fromhex(script_hex))) + + giver_address = Address(self.payment_vkey.hash(), network=self.NETWORK) + + builder = TransactionBuilder(self.chain_context) + builder.add_input_address(giver_address) + builder.add_output( + TransactionOutput(giver_address, 50000000, script=forty_two_script) + ) + + signed_tx = builder.build_and_sign([self.payment_skey], giver_address) + + print("############### Transaction created ###############") + print(signed_tx) + print(signed_tx.to_cbor_hex()) + print("############### Submitting transaction ###############") + self.chain_context.submit_tx(signed_tx) + time.sleep(6) + + # ----------- Spend script utxo --------------- + utxo_to_spend = UTxO( + TransactionInput(signed_tx.id, 0), signed_tx.transaction_body.outputs[0] + ) + + builder = TransactionBuilder(self.chain_context) + builder.add_input(utxo_to_spend) + signed_tx = builder.build_and_sign([self.payment_skey], giver_address) + print("############### Transaction created ###############") + print(signed_tx) + print(signed_tx.to_cbor_hex()) + print("############### Submitting transaction ###############") + self.chain_context.submit_tx(signed_tx) + @retry(tries=TEST_RETRIES, backoff=1.3, delay=2, jitter=(0, 10)) @pytest.mark.post_alonzo def test_transaction_chaining(self): diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index dd83fbb4..e296a510 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -218,6 +218,8 @@ def add_input(self, utxo: UTxO) -> TransactionBuilder: TransactionBuilder: Current transaction builder. """ self.inputs.append(utxo) + if utxo.output.script: + self._reference_scripts.append(utxo.output.script) return self def _consolidate_redeemer(self, redeemer): @@ -1403,7 +1405,11 @@ def build( for address in self.input_addresses: for utxo in self.context.utxos(address): - if utxo not in seen_utxos and utxo not in self.excluded_inputs: + if ( + utxo not in seen_utxos + and utxo not in self.excluded_inputs + and utxo.output.script is None + ): additional_utxo_pool.append(utxo) additional_amount += utxo.output.amount seen_utxos.add(utxo) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 31794480..ef110617 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -2041,12 +2041,6 @@ def test_build_witness_set_mixed_scripts(chain_context): assert witness_set.plutus_v2_script is None assert witness_set.plutus_v3_script is None - # Test with remove_dup_script=False - witness_set = builder.build_witness_set(remove_dup_script=False) - assert len(witness_set.plutus_v1_script) == 2 - assert len(witness_set.plutus_v2_script) == 1 - assert len(witness_set.plutus_v3_script) == 1 - def test_add_script_input_post_chang(chain_context): tx_builder = TransactionBuilder(chain_context) From 90d7f64fa1f278c20d07b528e00bfc3e047617cd Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 21 Sep 2025 11:20:38 -0700 Subject: [PATCH 2/2] unit tests --- test/pycardano/test_txbuilder.py | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index ef110617..111b2b84 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -2415,3 +2415,54 @@ def test_token_transfer_with_change(chain_context): change_output.amount.multi_asset[token_policy_id][token_name] == 1876083 - 382 ) + + +def test_spend_utxo_with_script(chain_context): + """Test that UTxOs with scripts are added as reference scripts when added directly.""" + utxo = UTxO( + TransactionInput.from_primitive( + [ + "a6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", + 0, + ] + ), + TransactionOutput( + Address.from_primitive( + "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + ), + Value(10000000), + script=PlutusV2Script(b"dummy test script"), + ), + ) + + tx_builder = TransactionBuilder(chain_context) + tx_builder.add_input(utxo) + assert len(tx_builder._reference_scripts) == 1 + + +def test_skip_utxo_with_script(chain_context): + """Test that UTxOs with scripts are skipped when selecting UTxOs by address.""" + addr = Address.from_primitive( + "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + ) + + utxo = UTxO( + TransactionInput.from_primitive( + [ + "a6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", + 0, + ] + ), + TransactionOutput( + addr, + Value(10000000), + script=PlutusV2Script(b"dummy test script"), + ), + ) + + with patch.object(chain_context, "utxos") as mock_utxos: + mock_utxos.return_value = [utxo] + tx_builder = TransactionBuilder(chain_context) + tx_builder.add_input_address(addr) + with pytest.raises(UTxOSelectionException): + tx_builder.build()