Skip to content

Commit 17a0d34

Browse files
KINGH242Hareem Adderleycffls
authored
Add support for NonEmptyOrderedSet in Plutus_data (#451)
* refactor: update script_data_hash to support NonEmptyOrderedSet for datums * fix: update plutus_data to support NonEmptyOrderedSet * fix: update plutus_data to use NonEmptyOrderedSet for improved data handling * refactor: enhance OrderedSet to support IndefiniteList for improved serialization * test: update test cases for script_data_hash to reflect changes * fix: update redeemer handling to use RedeemerMap for empty cases * fix: update datum handling to correctly process NonEmptyOrderedSet or list * fix: revert tests hash for lists and pass NonEmptyOrderedSet to script_data_hash in test_script_data_hash_redeemer_map * fix: update script_data_hash to pass in NonEmptyOrderedSet for datums * fix: improve redeemer handling for compatibility and remove canonical True on cost_models_bytes * fix: revert expected hash values in script_data_hash tests * Fix OrderedSet serialization * Remove unnecessary type ignore * fix: update plutus_data type to include IndefiniteList for improved compatibility --------- Co-authored-by: Hareem Adderley <[email protected]> Co-authored-by: Jerry <[email protected]>
1 parent 30712f4 commit 17a0d34

File tree

6 files changed

+130
-38
lines changed

6 files changed

+130
-38
lines changed

pycardano/serialization.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
List,
2525
Optional,
2626
Sequence,
27-
Set,
2827
Type,
2928
TypeVar,
3029
Union,
@@ -160,6 +159,7 @@ class RawCBOR:
160159
Fraction,
161160
FrozenList,
162161
IndefiniteFrozenList,
162+
ByteString,
163163
)
164164
"""
165165
A list of types that could be encoded by
@@ -1128,26 +1128,53 @@ def list_hook(
11281128
return lambda vals: [cls.from_primitive(v) for v in vals]
11291129

11301130

1131-
class OrderedSet(list, Generic[T], CBORSerializable):
1132-
def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True):
1131+
class OrderedSet(Generic[T], CBORSerializable):
1132+
def __init__(
1133+
self,
1134+
iterable: Optional[Union[List[T], IndefiniteList]] = None,
1135+
use_tag: bool = True,
1136+
):
11331137
super().__init__()
1134-
self._set: Set[str] = set()
1138+
self._dict: Dict[bytes, int] = {}
1139+
self._list: List[T] = []
11351140
self._use_tag = use_tag
1141+
self._is_indefinite_list = False
11361142
if iterable:
1143+
self._is_indefinite_list = isinstance(iterable, IndefiniteList)
11371144
self.extend(iterable)
11381145

11391146
def append(self, item: T) -> None:
1140-
item_key = str(item)
1141-
if item_key not in self._set:
1142-
super().append(item)
1143-
self._set.add(item_key)
1147+
if item in self:
1148+
return
1149+
self._list.append(item)
1150+
self._dict[dumps(item, default=default_encoder)] = len(self._list) - 1
11441151

11451152
def extend(self, items: Iterable[T]) -> None:
1153+
self._is_indefinite_list = isinstance(items, IndefiniteList)
11461154
for item in items:
11471155
self.append(item)
11481156

1157+
def remove(self, item: T) -> None:
1158+
if item not in self:
1159+
return
1160+
index = self._dict.pop(dumps(item, default=default_encoder))
1161+
self._list.pop(index)
1162+
# Update the indices in the dictionary
1163+
for key, idx in self._dict.items():
1164+
if idx > index:
1165+
self._dict[key] = idx - 1
1166+
11491167
def __contains__(self, item: object) -> bool:
1150-
return str(item) in self._set
1168+
return dumps(item, default=default_encoder) in self._dict
1169+
1170+
def __iter__(self):
1171+
return iter(self._list)
1172+
1173+
def __getitem__(self, index: int) -> T:
1174+
return self._list[index]
1175+
1176+
def __len__(self) -> int:
1177+
return len(self._list)
11511178

11521179
def __eq__(self, other: object) -> bool:
11531180
if not isinstance(other, OrderedSet):
@@ -1159,10 +1186,13 @@ def __eq__(self, other: object) -> bool:
11591186
def __repr__(self) -> str:
11601187
return f"{self.__class__.__name__}({list(self)})"
11611188

1162-
def to_shallow_primitive(self) -> Union[CBORTag, List[T]]:
1189+
def to_shallow_primitive(self) -> Union[CBORTag, Union[List[T], IndefiniteList]]:
11631190
if self._use_tag:
1164-
return CBORTag(258, list(self))
1165-
return list(self)
1191+
return CBORTag(
1192+
258,
1193+
IndefiniteList(list(self)) if self._is_indefinite_list else list(self),
1194+
)
1195+
return IndefiniteList(list(self)) if self._is_indefinite_list else list(self)
11661196

11671197
@classmethod
11681198
def from_primitive(
@@ -1195,7 +1225,11 @@ def __deepcopy__(self, memo):
11951225

11961226

11971227
class NonEmptyOrderedSet(OrderedSet[T]):
1198-
def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True):
1228+
def __init__(
1229+
self,
1230+
iterable: Optional[Union[List[T], IndefiniteList]] = None,
1231+
use_tag: bool = True,
1232+
):
11991233
super().__init__(iterable, use_tag)
12001234

12011235
def validate(self):

pycardano/txbuilder.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from copy import deepcopy
44
from dataclasses import dataclass, field, fields
5-
from typing import Dict, List, Optional, Set, Tuple, Union
5+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
66

77
from pycardano import RedeemerMap
88
from pycardano.address import Address, AddressType
@@ -616,7 +616,9 @@ def script_data_hash(self) -> Optional[ScriptDataHash]:
616616
)
617617
)
618618
return script_data_hash(
619-
self.redeemers(), list(self.datums.values()), CostModels(cost_models)
619+
self.redeemers(),
620+
NonEmptyOrderedSet(list(self.datums.values())),
621+
CostModels(cost_models),
620622
)
621623
else:
622624
return None
@@ -1170,6 +1172,7 @@ def build_witness_set(
11701172
plutus_v1_scripts: NonEmptyOrderedSet[PlutusV1Script] = NonEmptyOrderedSet()
11711173
plutus_v2_scripts: NonEmptyOrderedSet[PlutusV2Script] = NonEmptyOrderedSet()
11721174
plutus_v3_scripts: NonEmptyOrderedSet[PlutusV3Script] = NonEmptyOrderedSet()
1175+
plutus_data: NonEmptyOrderedSet[Any] = NonEmptyOrderedSet()
11731176

11741177
input_scripts = (
11751178
{
@@ -1181,6 +1184,9 @@ def build_witness_set(
11811184
else {}
11821185
)
11831186

1187+
for datum in self.datums.values():
1188+
plutus_data.append(datum)
1189+
11841190
for script in self.scripts:
11851191
if script_hash(script) not in input_scripts:
11861192
if isinstance(script, NativeScript):
@@ -1204,7 +1210,7 @@ def build_witness_set(
12041210
plutus_v2_script=plutus_v2_scripts if plutus_v2_scripts else None,
12051211
plutus_v3_script=plutus_v3_scripts if plutus_v3_scripts else None,
12061212
redeemer=self.redeemers() if self._redeemer_list else None,
1207-
plutus_data=list(self.datums.values()) if self.datums else None,
1213+
plutus_data=plutus_data if plutus_data else None,
12081214
)
12091215

12101216
def _ensure_no_input_exclusion_conflict(self):

pycardano/utils.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
from pycardano.backend.base import ChainContext
1414
from pycardano.hash import SCRIPT_DATA_HASH_SIZE, SCRIPT_HASH_SIZE, ScriptDataHash
15-
from pycardano.plutus import COST_MODELS, CostModels, Datum, Redeemers
16-
from pycardano.serialization import default_encoder
15+
from pycardano.plutus import COST_MODELS, CostModels, Datum, RedeemerMap, Redeemers
16+
from pycardano.serialization import NonEmptyOrderedSet, default_encoder
1717
from pycardano.transaction import MultiAsset, TransactionOutput, Value
1818

1919
__all__ = [
@@ -235,30 +235,35 @@ def min_lovelace_post_alonzo(output: TransactionOutput, context: ChainContext) -
235235

236236

237237
def script_data_hash(
238-
redeemers: Redeemers,
239-
datums: List[Datum],
238+
redeemers: Optional[Redeemers] = None,
239+
datums: Optional[Union[List[Datum], NonEmptyOrderedSet[Datum]]] = None,
240240
cost_models: Optional[Union[CostModels, Dict]] = None,
241241
) -> ScriptDataHash:
242242
"""Calculate plutus script data hash
243243
244244
Args:
245-
redeemers (Redeemers): Redeemers to include.
246-
datums (List[Datum]): Datums to include.
245+
redeemers (Optional[Redeemers]): Redeemers to include.
246+
datums (Optional[Union[List[Datum], NonEmptyOrderedSet[Datum]]]): Datums to include.
247247
cost_models (Optional[CostModels]): Cost models.
248248
249249
Returns:
250250
ScriptDataHash: Plutus script data hash
251251
"""
252-
if not redeemers:
252+
if redeemers is None:
253+
redeemers = RedeemerMap()
254+
cost_models = {}
255+
elif len(redeemers) == 0:
253256
cost_models = {}
254257
elif not cost_models:
255258
cost_models = COST_MODELS
256259

257260
redeemer_bytes = cbor2.dumps(redeemers, default=default_encoder)
261+
258262
if datums:
259263
datum_bytes = cbor2.dumps(datums, default=default_encoder)
260264
else:
261265
datum_bytes = b""
266+
262267
cost_models_bytes = cbor2.dumps(cost_models, default=default_encoder)
263268

264269
return ScriptDataHash(

pycardano/witness.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,13 @@
99

1010
from pycardano.key import ExtendedVerificationKey, VerificationKey
1111
from pycardano.nativescript import NativeScript
12-
from pycardano.plutus import (
13-
PlutusV1Script,
14-
PlutusV2Script,
15-
PlutusV3Script,
16-
RawPlutusData,
17-
Redeemers,
18-
)
12+
from pycardano.plutus import PlutusV1Script, PlutusV2Script, PlutusV3Script, Redeemers
1913
from pycardano.serialization import (
2014
ArrayCBORSerializable,
15+
IndefiniteList,
2116
MapCBORSerializable,
2217
NonEmptyOrderedSet,
2318
limit_primitive_type,
24-
list_hook,
2519
)
2620

2721
__all__ = ["VerificationKeyWitness", "TransactionWitnessSet"]
@@ -114,9 +108,11 @@ class TransactionWitnessSet(MapCBORSerializable):
114108
},
115109
)
116110

117-
plutus_data: Optional[List[Any]] = field(
118-
default=None,
119-
metadata={"optional": True, "key": 4, "object_hook": list_hook(RawPlutusData)},
111+
plutus_data: Optional[Union[IndefiniteList, List[Any], NonEmptyOrderedSet[Any]]] = (
112+
field(
113+
default=None,
114+
metadata={"optional": True, "key": 4},
115+
)
120116
)
121117

122118
redeemer: Optional[Redeemers] = field(
@@ -150,6 +146,10 @@ def __post_init__(self):
150146
self.vkey_witnesses = NonEmptyOrderedSet(self.vkey_witnesses)
151147
if isinstance(self.native_scripts, list):
152148
self.native_scripts = NonEmptyOrderedSet(self.native_scripts)
149+
if isinstance(self.plutus_data, list) and not isinstance(
150+
self.plutus_data, NonEmptyOrderedSet
151+
):
152+
self.plutus_data = NonEmptyOrderedSet(list(self.plutus_data))
153153
if isinstance(self.plutus_v1_script, list):
154154
self.plutus_v1_script = NonEmptyOrderedSet(self.plutus_v1_script)
155155
if isinstance(self.plutus_v2_script, list):

test/pycardano/test_serialization.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,23 @@ def test_ordered_set():
638638
assert list(s) == [1, 2, 3]
639639
assert s._use_tag
640640

641+
# Test remove
642+
s = OrderedSet([1, 2, 3, 4])
643+
s.remove(2)
644+
assert list(s) == [1, 3, 4]
645+
assert 2 not in s
646+
assert 1 in s
647+
assert 3 in s
648+
assert 4 in s
649+
s.remove(2)
650+
assert list(s) == [1, 3, 4]
651+
assert 2 not in s
652+
s.remove(3)
653+
assert list(s) == [1, 4]
654+
assert 3 not in s
655+
s.remove(4)
656+
assert list(s) == [1]
657+
641658

642659
def test_ordered_set_with_complex_types():
643660
# Test with VerificationKeyWitness
@@ -909,9 +926,9 @@ class MyOrderedSet(OrderedSet):
909926
assert 4 not in s
910927

911928
# Test with complex objects
912-
class TestObj:
913-
def __init__(self, value):
914-
self.value = value
929+
@dataclass(repr=False)
930+
class TestObj(ArrayCBORSerializable):
931+
value: str
915932

916933
def __str__(self):
917934
return f"TestObj({self.value})"

test/pycardano/test_util.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@
22

33
import pytest
44

5+
from pycardano import NonEmptyOrderedSet
56
from pycardano.hash import SCRIPT_HASH_SIZE, ScriptDataHash
6-
from pycardano.plutus import ExecutionUnits, PlutusData, Redeemer, RedeemerTag, Unit
7+
from pycardano.plutus import (
8+
COST_MODELS,
9+
ExecutionUnits,
10+
PlutusData,
11+
Redeemer,
12+
RedeemerKey,
13+
RedeemerMap,
14+
RedeemerTag,
15+
RedeemerValue,
16+
Unit,
17+
)
718
from pycardano.transaction import Value
819
from pycardano.utils import (
920
min_lovelace_pre_alonzo,
@@ -160,6 +171,25 @@ def test_script_data_hash():
160171
) == script_data_hash(redeemers=redeemers, datums=[unit])
161172

162173

174+
def test_script_data_hash_redeemer_map():
175+
unit = Unit()
176+
redeemer = Redeemer(42, ExecutionUnits(573240, 253056459))
177+
redeemer.tag = RedeemerTag.SPEND
178+
redeemers = RedeemerMap(
179+
{
180+
RedeemerKey(redeemer.tag, redeemer.index): RedeemerValue(
181+
redeemer.data, redeemer.ex_units
182+
)
183+
}
184+
)
185+
cost_models = COST_MODELS
186+
assert ScriptDataHash.from_primitive(
187+
"04ad5eb241d1ede2bbbd60c5853de7659d2ecfb1a29d6cbb6921ef7bdd46ca3c"
188+
) == script_data_hash(
189+
redeemers=redeemers, datums=NonEmptyOrderedSet([unit]), cost_models=cost_models
190+
)
191+
192+
163193
def test_script_data_hash_datum_only():
164194
unit = Unit()
165195
assert ScriptDataHash.from_primitive(

0 commit comments

Comments
 (0)