1
1
from __future__ import annotations
2
2
3
- from copy import copy , deepcopy
3
+ from copy import deepcopy
4
4
from dataclasses import dataclass , field , fields
5
5
from typing import Dict , List , Optional , Set , Tuple , Union
6
6
@@ -865,7 +865,9 @@ def build(
865
865
Returns:
866
866
TransactionBody: A transaction body.
867
867
"""
868
- self ._update_execution_units ()
868
+ self ._update_execution_units (
869
+ change_address , merge_change , collateral_change_address
870
+ )
869
871
self ._ensure_no_input_exclusion_conflict ()
870
872
selected_utxos = []
871
873
selected_amount = Value ()
@@ -1020,7 +1022,7 @@ def _set_collateral_return(self, collateral_return_address: Address):
1020
1022
):
1021
1023
return
1022
1024
1023
- if not collateral_return_address or self . _should_estimate_execution_units :
1025
+ if not collateral_return_address :
1024
1026
return
1025
1027
1026
1028
collateral_amount = (
@@ -1045,15 +1047,15 @@ def _add_collateral_input(cur_total, candidate_inputs):
1045
1047
cur_total += candidate .output .amount
1046
1048
1047
1049
sorted_inputs = sorted (
1048
- self .inputs .copy (), key = lambda i : i .output .amount , reverse = True
1050
+ self .inputs .copy (),
1051
+ key = lambda i : (len (i .output .to_cbor ()), - i .output .amount .coin ),
1049
1052
)
1050
1053
_add_collateral_input (tmp_val , sorted_inputs )
1051
1054
1052
1055
if tmp_val .coin < collateral_amount :
1053
1056
sorted_inputs = sorted (
1054
1057
self .context .utxos (str (collateral_return_address )),
1055
- key = lambda i : i .output .amount ,
1056
- reverse = True ,
1058
+ key = lambda i : (len (i .output .to_cbor ()), - i .output .amount .coin ),
1057
1059
)
1058
1060
_add_collateral_input (tmp_val , sorted_inputs )
1059
1061
@@ -1062,33 +1064,38 @@ def _add_collateral_input(cur_total, candidate_inputs):
1062
1064
for utxo in self .collaterals :
1063
1065
total_input += utxo .output .amount
1064
1066
1065
- # TODO: Remove this check when we are in Vasil era
1066
- if total_input .multi_asset :
1067
- if collateral_amount > total_input .coin :
1067
+ if collateral_amount > total_input .coin :
1068
+ raise ValueError (
1069
+ f"Minimum collateral amount { collateral_amount } is greater than total "
1070
+ f"provided collateral inputs { total_input } "
1071
+ )
1072
+ else :
1073
+ return_amount = total_input - collateral_amount
1074
+ min_lovelace_val = min_lovelace_post_alonzo (
1075
+ TransactionOutput (collateral_return_address , return_amount ),
1076
+ self .context ,
1077
+ )
1078
+ if min_lovelace_val > return_amount .coin :
1068
1079
raise ValueError (
1069
- f"Minimum collateral amount { collateral_amount } is greater than total "
1070
- f"provided collateral inputs { total_input } "
1080
+ f"Minimum lovelace amount for collateral return { min_lovelace_val } is "
1081
+ f"greater than collateral change { return_amount . coin } . Please provide more collateral inputs. "
1071
1082
)
1072
1083
else :
1073
- return_amount = total_input - collateral_amount
1074
- min_lovelace_val = min_lovelace_post_alonzo (
1075
- TransactionOutput (collateral_return_address , return_amount ),
1076
- self .context ,
1084
+ self ._collateral_return = TransactionOutput (
1085
+ collateral_return_address , total_input - collateral_amount
1077
1086
)
1078
- if min_lovelace_val > return_amount .coin :
1079
- raise ValueError (
1080
- f"Minimum lovelace amount for collateral return { min_lovelace_val } is "
1081
- f"greater than collateral change { return_amount .coin } . Please provide more collateral inputs."
1082
- )
1083
- else :
1084
- self ._collateral_return = TransactionOutput (
1085
- collateral_return_address , total_input - collateral_amount
1086
- )
1087
- self ._total_collateral = collateral_amount
1087
+ self ._total_collateral = collateral_amount
1088
1088
1089
- def _update_execution_units (self ):
1089
+ def _update_execution_units (
1090
+ self ,
1091
+ change_address : Optional [Address ] = None ,
1092
+ merge_change : bool = False ,
1093
+ collateral_change_address : Optional [Address ] = None ,
1094
+ ):
1090
1095
if self ._should_estimate_execution_units :
1091
- estimated_execution_units = self ._estimate_execution_units ()
1096
+ estimated_execution_units = self ._estimate_execution_units (
1097
+ change_address , merge_change , collateral_change_address
1098
+ )
1092
1099
for r in self .redeemers :
1093
1100
key = f"{ r .tag .name .lower ()} :{ r .index } "
1094
1101
if key not in estimated_execution_units :
@@ -1107,15 +1114,19 @@ def _update_execution_units(self):
1107
1114
def _estimate_execution_units (
1108
1115
self ,
1109
1116
change_address : Optional [Address ] = None ,
1117
+ merge_change : bool = False ,
1118
+ collateral_change_address : Optional [Address ] = None ,
1110
1119
):
1111
- # Create a shallow copy of current builder, so we won't mess up current builder's internal states
1120
+ # Create a deep copy of current builder, so we won't mess up current builder's internal states
1112
1121
tmp_builder = TransactionBuilder (self .context )
1113
1122
for f in fields (self ):
1114
1123
if f .name not in ("context" ,):
1115
- setattr (tmp_builder , f .name , copy (getattr (self , f .name )))
1124
+ setattr (tmp_builder , f .name , deepcopy (getattr (self , f .name )))
1116
1125
tmp_builder ._should_estimate_execution_units = False
1117
1126
self ._should_estimate_execution_units = False
1118
- tx_body = tmp_builder .build (change_address )
1127
+ tx_body = tmp_builder .build (
1128
+ change_address , merge_change , collateral_change_address
1129
+ )
1119
1130
witness_set = tmp_builder ._build_fake_witness_set ()
1120
1131
tx = Transaction (
1121
1132
tx_body , witness_set , auxiliary_data = tmp_builder .auxiliary_data
@@ -1128,6 +1139,7 @@ def build_and_sign(
1128
1139
signing_keys : List [Union [SigningKey , ExtendedSigningKey ]],
1129
1140
change_address : Optional [Address ] = None ,
1130
1141
merge_change : Optional [bool ] = False ,
1142
+ collateral_change_address : Optional [Address ] = None ,
1131
1143
) -> Transaction :
1132
1144
"""Build a transaction body from all constraints set through the builder and sign the transaction with
1133
1145
provided signing keys.
@@ -1139,12 +1151,17 @@ def build_and_sign(
1139
1151
transaction body will likely be unbalanced (sum of inputs is greater than the sum of outputs).
1140
1152
merge_change (Optional[bool]): If the change address match one of the transaction output, the change amount
1141
1153
will be directly added to that transaction output, instead of being added as a separate output.
1154
+ collateral_change_address (Optional[Address]): Address to which collateral changes will be returned.
1142
1155
1143
1156
Returns:
1144
1157
Transaction: A signed transaction.
1145
1158
"""
1146
1159
1147
- tx_body = self .build (change_address = change_address , merge_change = merge_change )
1160
+ tx_body = self .build (
1161
+ change_address = change_address ,
1162
+ merge_change = merge_change ,
1163
+ collateral_change_address = collateral_change_address ,
1164
+ )
1148
1165
witness_set = self .build_witness_set ()
1149
1166
witness_set .vkey_witnesses = []
1150
1167
0 commit comments