Skip to content

Commit 4245029

Browse files
authored
Test for correctness of the supplied script (#322)
* Test for correctness of the supplied script This is to avoid users accidentally adding script inputs with the wrong script and then get cryptic errors from ogmios. * Fix qa and add test cases for missing/incorrect scripts * Add test case for entirely missing script * Remove redundant check * Improve error message
1 parent 0fec24a commit 4245029

File tree

2 files changed

+208
-12
lines changed

2 files changed

+208
-12
lines changed

pycardano/txbuilder.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -259,24 +259,42 @@ def add_script_input(
259259
self._consolidate_redeemer(redeemer)
260260
self._inputs_to_redeemers[utxo] = redeemer
261261

262+
input_script_hash = utxo.output.address.payment_part
263+
264+
# collect potential scripts to fulfill the input
265+
candidate_scripts: List[
266+
Tuple[Union[NativeScript, PlutusV1Script, PlutusV2Script], Optional[UTxO]]
267+
] = []
262268
if utxo.output.script:
263-
self._inputs_to_scripts[utxo] = utxo.output.script
264-
self.reference_inputs.add(utxo)
265-
self._reference_scripts.append(utxo.output.script)
269+
candidate_scripts.append((utxo.output.script, utxo))
266270
elif not script:
267271
for i in self.context.utxos(utxo.output.address):
268272
if i.output.script:
269-
self._inputs_to_scripts[utxo] = i.output.script
270-
self.reference_inputs.add(i)
271-
self._reference_scripts.append(i.output.script)
272-
break
273+
candidate_scripts.append((i.output.script, i))
273274
elif isinstance(script, UTxO):
274-
assert script.output.script is not None
275-
self._inputs_to_scripts[utxo] = script.output.script
276-
self.reference_inputs.add(script)
277-
self._reference_scripts.append(script.output.script)
275+
if script.output.script is None:
276+
raise InvalidArgumentException(
277+
f"Expect the output of the reference UTxO {utxo}"
278+
" to have a script, but got None instead."
279+
)
280+
candidate_scripts.append((script.output.script, script))
278281
else:
279-
self._inputs_to_scripts[utxo] = script
282+
candidate_scripts.append((script, None))
283+
284+
found_valid_script = False
285+
for candidate_script, candidate_utxo in candidate_scripts:
286+
if script_hash(candidate_script) != input_script_hash:
287+
continue
288+
found_valid_script = True
289+
self._inputs_to_scripts[utxo] = candidate_script
290+
if candidate_utxo is not None:
291+
self.reference_inputs.add(candidate_utxo)
292+
self._reference_scripts.append(candidate_script)
293+
if not found_valid_script:
294+
raise InvalidArgumentException(
295+
f"Cannot find a valid script to fulfill the input UTxO: {utxo.input}."
296+
"Supplied scripts do not match the payment part of the input address."
297+
)
280298

281299
self.inputs.append(utxo)
282300
return self

test/pycardano/test_txbuilder.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,32 @@ def test_add_script_input_no_script(chain_context):
576576
assert witness.plutus_v1_script is None
577577

578578

579+
def test_add_script_input_payment_script(chain_context):
580+
tx_builder = TransactionBuilder(chain_context)
581+
tx_in1 = TransactionInput.from_primitive(
582+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
583+
)
584+
plutus_script = PlutusV1Script(b"dummy test script")
585+
vk1 = VerificationKey.from_cbor(
586+
"58206443a101bdb948366fc87369336224595d36d8b0eee5602cba8b81a024e58473"
587+
)
588+
script_address = Address(vk1.hash())
589+
datum = PlutusData()
590+
utxo1 = UTxO(
591+
tx_in1,
592+
TransactionOutput(script_address, 10000000, datum_hash=datum.hash()),
593+
)
594+
redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
595+
pytest.raises(
596+
InvalidArgumentException,
597+
tx_builder.add_script_input,
598+
utxo1,
599+
datum=datum,
600+
redeemer=redeemer,
601+
script=plutus_script,
602+
)
603+
604+
579605
def test_add_script_input_find_script(chain_context):
580606
original_utxos = chain_context.utxos(
581607
"addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"
@@ -658,6 +684,158 @@ def test_add_script_input_with_script_from_specified_utxo(chain_context):
658684
assert [existing_script_utxo.input] == tx_body.reference_inputs
659685

660686

687+
def test_add_script_input_incorrect_script(chain_context):
688+
tx_builder = TransactionBuilder(chain_context)
689+
tx_in1 = TransactionInput.from_primitive(
690+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
691+
)
692+
tx_in2 = TransactionInput.from_primitive(
693+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 1]
694+
)
695+
plutus_script = PlutusV1Script(b"dummy test script")
696+
script_hash = plutus_script_hash(plutus_script)
697+
incorrect_plutus_script = PlutusV2Script(b"dummy test script2")
698+
script_address = Address(script_hash)
699+
datum = PlutusData()
700+
utxo1 = UTxO(
701+
tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash())
702+
)
703+
mint = MultiAsset.from_primitive({script_hash.payload: {b"TestToken": 1}})
704+
UTxO(
705+
tx_in2,
706+
TransactionOutput(
707+
script_address, Value(10000000, mint), datum_hash=datum.hash()
708+
),
709+
)
710+
redeemer1 = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
711+
pytest.raises(
712+
InvalidArgumentException,
713+
tx_builder.add_script_input,
714+
utxo1,
715+
script=incorrect_plutus_script,
716+
datum=datum,
717+
redeemer=redeemer1,
718+
)
719+
720+
721+
def test_add_script_input_no_script_no_attached_script(chain_context):
722+
tx_builder = TransactionBuilder(chain_context)
723+
tx_in1 = TransactionInput.from_primitive(
724+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
725+
)
726+
plutus_script = PlutusV1Script(b"dummy test script")
727+
script_hash = plutus_script_hash(plutus_script)
728+
script_address = Address(script_hash)
729+
datum = PlutusData()
730+
utxo1 = UTxO(
731+
tx_in1,
732+
TransactionOutput(script_address, 10000000, datum_hash=datum.hash()),
733+
)
734+
redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
735+
pytest.raises(
736+
InvalidArgumentException,
737+
tx_builder.add_script_input,
738+
utxo1,
739+
datum=datum,
740+
redeemer=redeemer,
741+
)
742+
743+
744+
def test_add_script_input_find_incorrect_script(chain_context):
745+
original_utxos = chain_context.utxos(
746+
"addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"
747+
)
748+
with patch.object(chain_context, "utxos") as mock_utxos:
749+
tx_builder = TransactionBuilder(chain_context)
750+
tx_in1 = TransactionInput.from_primitive(
751+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
752+
)
753+
plutus_script = PlutusV1Script(b"dummy test script")
754+
incorrect_plutus_script = PlutusV2Script(b"dummy test script2")
755+
script_hash = plutus_script_hash(plutus_script)
756+
script_address = Address(script_hash)
757+
datum = PlutusData()
758+
utxo1 = UTxO(
759+
tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash())
760+
)
761+
762+
existing_script_utxo = UTxO(
763+
TransactionInput.from_primitive(
764+
[
765+
"41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7",
766+
1,
767+
]
768+
),
769+
TransactionOutput(script_address, 1234567, script=incorrect_plutus_script),
770+
)
771+
772+
mock_utxos.return_value = original_utxos + [existing_script_utxo]
773+
774+
redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
775+
pytest.raises(
776+
InvalidArgumentException,
777+
tx_builder.add_script_input,
778+
utxo1,
779+
datum=datum,
780+
redeemer=redeemer,
781+
)
782+
783+
784+
def test_add_script_input_with_script_from_specified_utxo_with_incorrect_script(
785+
chain_context,
786+
):
787+
tx_builder = TransactionBuilder(chain_context)
788+
tx_in1 = TransactionInput.from_primitive(
789+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
790+
)
791+
plutus_script = PlutusV2Script(b"dummy test script")
792+
incorrect_plutus_script = PlutusV1Script(b"dummy test script2")
793+
script_hash = plutus_script_hash(plutus_script)
794+
script_address = Address(script_hash)
795+
datum = PlutusData()
796+
utxo1 = UTxO(
797+
tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash())
798+
)
799+
800+
existing_script_utxo = UTxO(
801+
TransactionInput.from_primitive(
802+
[
803+
"41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7",
804+
1,
805+
]
806+
),
807+
TransactionOutput(script_address, 1234567, script=incorrect_plutus_script),
808+
)
809+
810+
redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000))
811+
pytest.raises(
812+
InvalidArgumentException,
813+
tx_builder.add_script_input,
814+
utxo1,
815+
script=existing_script_utxo,
816+
datum=datum,
817+
redeemer=redeemer,
818+
)
819+
820+
existing_script_utxo = UTxO(
821+
TransactionInput.from_primitive(
822+
[
823+
"41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7",
824+
1,
825+
]
826+
),
827+
TransactionOutput(script_address, 1234567, script=None),
828+
)
829+
pytest.raises(
830+
InvalidArgumentException,
831+
tx_builder.add_script_input,
832+
utxo1,
833+
script=existing_script_utxo,
834+
datum=datum,
835+
redeemer=redeemer,
836+
)
837+
838+
661839
def test_add_script_input_multiple_redeemers(chain_context):
662840
tx_builder = TransactionBuilder(chain_context)
663841
tx_in1 = TransactionInput.from_primitive(

0 commit comments

Comments
 (0)