1
1
from __future__ import annotations
2
2
3
3
from copy import deepcopy
4
- from typing import List , Optional , Set , Union
4
+ from typing import Dict , List , Optional , Set , Union
5
5
6
6
from pycardano .address import Address
7
7
from pycardano .backend .base import ChainContext
17
17
TransactionBuilderException ,
18
18
UTxOSelectionException ,
19
19
)
20
- from pycardano .hash import ScriptDataHash , ScriptHash , VerificationKeyHash
20
+ from pycardano .hash import DatumHash , ScriptDataHash , ScriptHash , VerificationKeyHash
21
21
from pycardano .key import ExtendedSigningKey , SigningKey , VerificationKey
22
22
from pycardano .logging import logger
23
23
from pycardano .metadata import AuxiliaryData
24
24
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
+ )
26
33
from pycardano .transaction import (
27
34
Asset ,
28
35
AssetName ,
@@ -74,10 +81,11 @@ def __init__(
74
81
self ._native_scripts = None
75
82
self ._mint = None
76
83
self ._required_signers = None
77
- self ._scripts = []
78
- self ._datums = []
84
+ self ._scripts = {}
85
+ self ._datums = {}
79
86
self ._redeemers = []
80
87
self ._inputs_to_redeemers = {}
88
+ self ._inputs_to_scripts = {}
81
89
self ._collaterals = []
82
90
83
91
if utxo_selectors :
@@ -121,10 +129,11 @@ def add_script_input(
121
129
f"Datum hash in transaction output is { utxo .output .datum_hash } , "
122
130
f"but actual datum hash from input datum is { datum .hash ()} ."
123
131
)
124
- self .scripts . append (script )
125
- self .datums . append ( datum )
132
+ self .scripts [ plutus_script_hash (script )] = script
133
+ self .datums [ datum . hash ()] = datum
126
134
self .redeemers .append (redeemer )
127
135
self ._inputs_to_redeemers [utxo ] = redeemer
136
+ self ._inputs_to_scripts [utxo ] = script
128
137
self .inputs .append (utxo )
129
138
return self
130
139
@@ -163,7 +172,7 @@ def add_output(
163
172
tx_out .datum_hash = datum_hash (datum )
164
173
self .outputs .append (tx_out )
165
174
if add_datum_to_witness :
166
- self .datums . append ( datum )
175
+ self .datums [ datum . hash ()] = datum
167
176
return self
168
177
169
178
@property
@@ -243,11 +252,11 @@ def required_signers(self, signers: List[VerificationKeyHash]):
243
252
self ._required_signers = signers
244
253
245
254
@property
246
- def scripts (self ) -> List [ bytes ]:
255
+ def scripts (self ) -> Dict [ ScriptHash , bytes ]:
247
256
return self ._scripts
248
257
249
258
@property
250
- def datums (self ) -> List [ Datum ]:
259
+ def datums (self ) -> Dict [ DatumHash , Datum ]:
251
260
return self ._datums
252
261
253
262
@property
@@ -265,7 +274,7 @@ def collaterals(self, collaterals: List[UTxO]):
265
274
@property
266
275
def script_data_hash (self ) -> Optional [ScriptDataHash ]:
267
276
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 ()) )
269
278
else :
270
279
return None
271
280
@@ -291,10 +300,6 @@ def _calc_change(
291
300
)
292
301
293
302
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" )
298
303
299
304
# Remove any asset that has 0 quantity
300
305
if change .multi_asset :
@@ -495,9 +500,30 @@ def _dfs(script: NativeScript):
495
500
return results
496
501
497
502
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
+
498
513
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
+ ):
500
518
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
+ )
501
527
self .redeemers .sort (key = lambda r : r .index )
502
528
503
529
def _build_tx_body (self ) -> TransactionBody :
@@ -553,9 +579,9 @@ def build_witness_set(self) -> TransactionWitnessSet:
553
579
"""
554
580
return TransactionWitnessSet (
555
581
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 ,
557
583
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 ,
559
585
)
560
586
561
587
def _ensure_no_input_exclusion_conflict (self ):
@@ -611,7 +637,11 @@ def build(self, change_address: Optional[Address] = None) -> TransactionBody:
611
637
additional_amount = Value ()
612
638
for address in self .input_addresses :
613
639
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
+ ):
615
645
additional_utxo_pool .append (utxo )
616
646
additional_amount += utxo .output .amount
617
647
0 commit comments