Skip to content

Commit ffb1d44

Browse files
committed
Support mint redeemer
1 parent 8e8927f commit ffb1d44

File tree

3 files changed

+98
-41
lines changed

3 files changed

+98
-41
lines changed

pycardano/txbuilder.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from copy import deepcopy
4-
from typing import List, Optional, Set, Union
4+
from typing import Dict, List, Optional, Set, Union
55

66
from pycardano.address import Address
77
from pycardano.backend.base import ChainContext
@@ -17,12 +17,19 @@
1717
TransactionBuilderException,
1818
UTxOSelectionException,
1919
)
20-
from pycardano.hash import ScriptDataHash, ScriptHash, VerificationKeyHash
20+
from pycardano.hash import DatumHash, ScriptDataHash, ScriptHash, VerificationKeyHash
2121
from pycardano.key import ExtendedSigningKey, SigningKey, VerificationKey
2222
from pycardano.logging import logger
2323
from pycardano.metadata import AuxiliaryData
2424
from pycardano.nativescript import NativeScript, ScriptAll, ScriptAny, ScriptPubkey
25-
from pycardano.plutus import Datum, ExecutionUnits, Redeemer, datum_hash
25+
from pycardano.plutus import (
26+
Datum,
27+
ExecutionUnits,
28+
Redeemer,
29+
RedeemerTag,
30+
datum_hash,
31+
plutus_script_hash,
32+
)
2633
from pycardano.transaction import (
2734
Asset,
2835
AssetName,
@@ -74,10 +81,11 @@ def __init__(
7481
self._native_scripts = None
7582
self._mint = None
7683
self._required_signers = None
77-
self._scripts = []
78-
self._datums = []
84+
self._scripts = {}
85+
self._datums = {}
7986
self._redeemers = []
8087
self._inputs_to_redeemers = {}
88+
self._inputs_to_scripts = {}
8189
self._collaterals = []
8290

8391
if utxo_selectors:
@@ -121,10 +129,11 @@ def add_script_input(
121129
f"Datum hash in transaction output is {utxo.output.datum_hash}, "
122130
f"but actual datum hash from input datum is {datum.hash()}."
123131
)
124-
self.scripts.append(script)
125-
self.datums.append(datum)
132+
self.scripts[plutus_script_hash(script)] = script
133+
self.datums[datum.hash()] = datum
126134
self.redeemers.append(redeemer)
127135
self._inputs_to_redeemers[utxo] = redeemer
136+
self._inputs_to_scripts[utxo] = script
128137
self.inputs.append(utxo)
129138
return self
130139

@@ -163,7 +172,7 @@ def add_output(
163172
tx_out.datum_hash = datum_hash(datum)
164173
self.outputs.append(tx_out)
165174
if add_datum_to_witness:
166-
self.datums.append(datum)
175+
self.datums[datum.hash()] = datum
167176
return self
168177

169178
@property
@@ -243,11 +252,11 @@ def required_signers(self, signers: List[VerificationKeyHash]):
243252
self._required_signers = signers
244253

245254
@property
246-
def scripts(self) -> List[bytes]:
255+
def scripts(self) -> Dict[ScriptHash, bytes]:
247256
return self._scripts
248257

249258
@property
250-
def datums(self) -> List[Datum]:
259+
def datums(self) -> Dict[DatumHash, Datum]:
251260
return self._datums
252261

253262
@property
@@ -265,7 +274,7 @@ def collaterals(self, collaterals: List[UTxO]):
265274
@property
266275
def script_data_hash(self) -> Optional[ScriptDataHash]:
267276
if self.datums or self.redeemers:
268-
return script_data_hash(self.redeemers, self.datums)
277+
return script_data_hash(self.redeemers, list(self.datums.values()))
269278
else:
270279
return None
271280

@@ -291,10 +300,6 @@ def _calc_change(
291300
)
292301

293302
change = provided - requested
294-
if change.coin < 0:
295-
# We assign max fee for now to ensure enough balance regardless of splits condition
296-
# We can implement a more precise fee logic and requirements later
297-
raise InsufficientUTxOBalanceException("Not enough ADA to cover fees")
298303

299304
# Remove any asset that has 0 quantity
300305
if change.multi_asset:
@@ -495,9 +500,30 @@ def _dfs(script: NativeScript):
495500
return results
496501

497502
def _set_redeemer_index(self):
503+
# Set redeemers' index according to section 4.1 in
504+
# https://hydra.iohk.io/build/13099856/download/1/alonzo-changes.pdf
505+
506+
if self.mint:
507+
sorted_mint_policies = sorted(
508+
self.mint.keys(), key=lambda x: x.to_cbor("bytes")
509+
)
510+
else:
511+
sorted_mint_policies = []
512+
498513
for i, utxo in enumerate(self.inputs):
499-
if utxo in self._inputs_to_redeemers:
514+
if (
515+
utxo in self._inputs_to_redeemers
516+
and self._inputs_to_redeemers[utxo].tag == RedeemerTag.SPEND
517+
):
500518
self._inputs_to_redeemers[utxo].index = i
519+
elif (
520+
utxo in self._inputs_to_redeemers
521+
and self._inputs_to_redeemers[utxo].tag == RedeemerTag.MINT
522+
):
523+
redeemer = self._inputs_to_redeemers[utxo]
524+
redeemer.index = sorted_mint_policies.index(
525+
plutus_script_hash(self._inputs_to_scripts[utxo])
526+
)
501527
self.redeemers.sort(key=lambda r: r.index)
502528

503529
def _build_tx_body(self) -> TransactionBody:
@@ -553,9 +579,9 @@ def build_witness_set(self) -> TransactionWitnessSet:
553579
"""
554580
return TransactionWitnessSet(
555581
native_scripts=self.native_scripts,
556-
plutus_script=self.scripts if self.scripts else None,
582+
plutus_script=list(self.scripts.values()) if self.scripts else None,
557583
redeemer=self.redeemers if self.redeemers else None,
558-
plutus_data=self.datums if self.datums else None,
584+
plutus_data=list(self.datums.values()) if self.datums else None,
559585
)
560586

561587
def _ensure_no_input_exclusion_conflict(self):
@@ -611,7 +637,11 @@ def build(self, change_address: Optional[Address] = None) -> TransactionBody:
611637
additional_amount = Value()
612638
for address in self.input_addresses:
613639
for utxo in self.context.utxos(str(address)):
614-
if utxo not in selected_utxos and utxo not in self.excluded_inputs:
640+
if (
641+
utxo not in selected_utxos
642+
and utxo not in self.excluded_inputs
643+
and not utxo.output.datum_hash # UTxO with datum should be added by using `add_script_input`
644+
):
615645
additional_utxo_pool.append(utxo)
616646
additional_amount += utxo.output.amount
617647

pycardano/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from pycardano.backend.base import ChainContext
1212
from pycardano.hash import SCRIPT_DATA_HASH_SIZE, SCRIPT_HASH_SIZE, ScriptDataHash
13-
from pycardano.plutus import COST_MODELS, CostModels, PlutusData, Redeemer
13+
from pycardano.plutus import COST_MODELS, CostModels, Datum, Redeemer
1414
from pycardano.serialization import default_encoder
1515
from pycardano.transaction import MultiAsset, Value
1616

@@ -116,14 +116,14 @@ def min_lovelace(
116116

117117
def script_data_hash(
118118
redeemers: List[Redeemer],
119-
datums: List[PlutusData],
119+
datums: List[Datum],
120120
cost_models: Optional[CostModels] = None,
121121
) -> ScriptDataHash:
122122
"""Calculate plutus script data hash
123123
124124
Args:
125125
redeemers (List[Redeemer]): Redeemers to include.
126-
datums (List[PlutusData]): Datums to include.
126+
datums (List[Datum]): Datums to include.
127127
cost_models (Optional[CostModels]): Cost models.
128128
129129
Returns:

test/pycardano/test_txbuilder.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,20 @@
2222
ScriptAll,
2323
ScriptPubkey,
2424
)
25-
from pycardano.plutus import ExecutionUnits, PlutusData, Redeemer, RedeemerTag
26-
from pycardano.transaction import MultiAsset, TransactionInput, TransactionOutput, UTxO
25+
from pycardano.plutus import (
26+
ExecutionUnits,
27+
PlutusData,
28+
Redeemer,
29+
RedeemerTag,
30+
plutus_script_hash,
31+
)
32+
from pycardano.transaction import (
33+
MultiAsset,
34+
TransactionInput,
35+
TransactionOutput,
36+
UTxO,
37+
Value,
38+
)
2739
from pycardano.txbuilder import TransactionBuilder
2840
from pycardano.witness import VerificationKeyWitness
2941

@@ -327,39 +339,54 @@ def test_not_enough_input_amount(chain_context):
327339

328340
def test_add_script_input(chain_context):
329341
tx_builder = TransactionBuilder(chain_context)
330-
tx_in = TransactionInput.from_primitive(
342+
tx_in1 = TransactionInput.from_primitive(
331343
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0]
332344
)
333-
plutus_script = b"dummy test script"
334-
script_hash = ScriptHash(
335-
blake2b(
336-
bytes(1) + cbor2.dumps(plutus_script), SCRIPT_HASH_SIZE, encoder=RawEncoder
337-
)
345+
tx_in2 = TransactionInput.from_primitive(
346+
["18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 1]
338347
)
348+
plutus_script = b"dummy test script"
349+
script_hash = plutus_script_hash(plutus_script)
339350
script_address = Address(script_hash)
340351
datum = PlutusData()
341-
tx_out = TransactionOutput(script_address, 10000000, datum_hash=datum.hash())
342-
utxo = UTxO(tx_in, tx_out)
343-
redeemer = Redeemer(
352+
utxo1 = UTxO(
353+
tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash())
354+
)
355+
mint = MultiAsset.from_primitive({script_hash.payload: {b"TestToken": 1}})
356+
utxo2 = UTxO(
357+
tx_in2,
358+
TransactionOutput(
359+
script_address, Value(10000000, mint), datum_hash=datum.hash()
360+
),
361+
)
362+
redeemer1 = Redeemer(
344363
RedeemerTag.SPEND, PlutusData(), ExecutionUnits(1000000, 1000000)
345364
)
346-
tx_builder.add_script_input(utxo, plutus_script, datum, redeemer)
365+
redeemer2 = Redeemer(
366+
RedeemerTag.MINT, PlutusData(), ExecutionUnits(1000000, 1000000)
367+
)
368+
tx_builder.mint = mint
369+
tx_builder.add_script_input(utxo1, plutus_script, datum, redeemer1)
370+
tx_builder.add_script_input(utxo2, plutus_script, datum, redeemer2)
347371
receiver = Address.from_primitive(
348372
"addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"
349373
)
350374
tx_builder.add_output(TransactionOutput(receiver, 5000000))
351375
tx_body = tx_builder.build(change_address=receiver)
352376
witness = tx_builder.build_witness_set()
353377
assert [datum] == witness.plutus_data
354-
assert [redeemer] == witness.redeemer
378+
assert [redeemer1, redeemer2] == witness.redeemer
355379
assert [plutus_script] == witness.plutus_script
356380
assert (
357-
"a4008182582018cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad2143159"
358-
"0f7e6643438ef00018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb3"
359-
"0d2ac960262444f6e5f41a004c4b4082581d60f6532850e1bccee9c72a9113a"
360-
"d98bcc5dbb30d2ac960262444f6e5f41a0048e737021a000364090b5820032d"
361-
"812ee0731af78fe4ec67e4d30d16313c09e6fb675af28f825797e8b5621d"
362-
== tx_body.to_cbor()
381+
"a5008282582018cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad2143159"
382+
"0f7e6643438ef0082582018cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1"
383+
"ad21431590f7e6643438ef01018282581d60f6532850e1bccee9c72a9113ad9"
384+
"8bcc5dbb30d2ac960262444f6e5f41a004c4b4082581d60f6532850e1bccee9"
385+
"c72a9113ad98bcc5dbb30d2ac960262444f6e5f4821a00e083cfa1581c876f1"
386+
"9078b059c928258d848c8cd871864d281eb6776ed7f80b68536a14954657374"
387+
"546f6b656e02021a00045df109a1581c876f19078b059c928258d848c8cd871"
388+
"864d281eb6776ed7f80b68536a14954657374546f6b656e010b5820c0978261"
389+
"d9818d92926eb031d38d141f513a05478d697555f32edf6443ebeb08" == tx_body.to_cbor()
363390
)
364391

365392

0 commit comments

Comments
 (0)