Skip to content

Commit 22f9c63

Browse files
authored
Policy json serializer (#58)
* Read/write native scripts to/from dict * Differentiate between ints and strings for to_dict * Add json_tag/field params to each native script * Format with black * Add docstring * Add to/from_dict tests
1 parent 62161ad commit 22f9c63

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

pycardano/nativescript.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, field
6-
from typing import List, Union
6+
from typing import ClassVar, List, Union
77

88
from nacl.encoding import RawEncoder
99
from nacl.hash import blake2b
@@ -52,16 +52,80 @@ def hash(self) -> ScriptHash:
5252
)
5353
)
5454

55+
@classmethod
56+
def from_dict(
57+
cls: NativeScript, script: dict, top_level: bool = True
58+
) -> Union[
59+
ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter
60+
]:
61+
"""Parse a standard native script dictionary (potentially parsed from a JSON file)."""
62+
63+
types = {
64+
p.json_tag: p
65+
for p in [
66+
ScriptPubkey,
67+
ScriptAll,
68+
ScriptAny,
69+
ScriptNofK,
70+
InvalidBefore,
71+
InvalidHereAfter,
72+
]
73+
}
74+
75+
if isinstance(script, dict):
76+
native_script = []
77+
78+
for key, value in script.items():
79+
if key == "type":
80+
native_script.insert(0, list(types.keys()).index(value))
81+
elif key == "scripts":
82+
native_script.append(cls.from_dict(value, top_level=False))
83+
else:
84+
native_script.append(value)
85+
86+
elif isinstance(script, list): # list
87+
native_script = [cls.from_dict(i, top_level=False) for i in script]
88+
89+
if not top_level:
90+
return native_script
91+
else:
92+
return super(NativeScript, types[script["type"]]).from_primitive(
93+
native_script[1:]
94+
)
95+
96+
def to_dict(self) -> dict:
97+
"""Export to standard native script dictionary (potentially to dump to a JSON file)."""
98+
99+
script = {}
100+
101+
for value in self.__dict__.values():
102+
script["type"] = self.json_tag
103+
104+
if isinstance(value, list):
105+
script["scripts"] = [i.to_dict() for i in value]
106+
107+
else:
108+
if isinstance(value, int):
109+
script[self.json_field] = value
110+
else:
111+
script[self.json_field] = str(value)
112+
113+
return script
114+
55115

56116
@dataclass
57117
class ScriptPubkey(NativeScript):
118+
json_tag: ClassVar[str] = "sig"
119+
json_field: ClassVar[str] = "keyHash"
58120
_TYPE: int = field(default=0, init=False)
59121

60122
key_hash: VerificationKeyHash
61123

62124

63125
@dataclass
64126
class ScriptAll(NativeScript):
127+
json_tag: ClassVar[str] = "all"
128+
json_field: ClassVar[str] = "scripts"
65129
_TYPE: int = field(default=1, init=False)
66130

67131
native_scripts: List[
@@ -78,6 +142,8 @@ class ScriptAll(NativeScript):
78142

79143
@dataclass
80144
class ScriptAny(NativeScript):
145+
json_tag: ClassVar[str] = "any"
146+
json_field: ClassVar[str] = "scripts"
81147
_TYPE: int = field(default=2, init=False)
82148

83149
native_scripts: List[
@@ -94,6 +160,8 @@ class ScriptAny(NativeScript):
94160

95161
@dataclass
96162
class ScriptNofK(NativeScript):
163+
json_tag: ClassVar[str] = "atLeast"
164+
json_field: ClassVar[str] = "required"
97165
_TYPE: int = field(default=3, init=False)
98166

99167
n: int
@@ -112,13 +180,17 @@ class ScriptNofK(NativeScript):
112180

113181
@dataclass
114182
class InvalidBefore(NativeScript):
183+
json_tag: ClassVar[str] = "after"
184+
json_field: ClassVar[str] = "slot"
115185
_TYPE: int = field(default=4, init=False)
116186

117187
before: int = None
118188

119189

120190
@dataclass
121191
class InvalidHereAfter(NativeScript):
192+
json_tag: ClassVar[str] = "before"
193+
json_field: ClassVar[str] = "slot"
122194
_TYPE: int = field(default=5, init=False)
123195

124196
after: int = None

test/pycardano/test_nativescript.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pycardano.nativescript import (
88
InvalidBefore,
99
InvalidHereAfter,
10+
NativeScript,
1011
ScriptAll,
1112
ScriptAny,
1213
ScriptNofK,
@@ -123,3 +124,75 @@ def test_full_tx():
123124
script = ScriptAll([before, spk1, spk2])
124125

125126
assert tx.transaction_witness_set.native_scripts[0] == script
127+
128+
129+
def test_to_dict():
130+
131+
vk1 = VerificationKey.from_cbor(
132+
"58206443a101bdb948366fc87369336224595d36d8b0eee5602cba8b81a024e58473"
133+
)
134+
vk2 = VerificationKey.from_cbor(
135+
"58206443a101bdb948366fc87369336224595d36d8b0eee5602cba8b81a024e58475"
136+
)
137+
spk1 = ScriptPubkey(key_hash=vk1.hash())
138+
spk2 = ScriptPubkey(key_hash=vk2.hash())
139+
before = InvalidHereAfter(123456789)
140+
after = InvalidBefore(123456780)
141+
142+
script_nofk = ScriptNofK(2, [before, after, spk1, spk2])
143+
144+
script_dict = script_nofk.to_dict()
145+
146+
assert script_dict == {
147+
"type": "atLeast",
148+
"required": 2,
149+
"scripts": [
150+
{"type": "before", "slot": 123456789},
151+
{"type": "after", "slot": 123456780},
152+
{
153+
"type": "sig",
154+
"keyHash": "9139e5c0a42f0f2389634c3dd18dc621f5594c5ba825d9a8883c6627",
155+
},
156+
{
157+
"type": "sig",
158+
"keyHash": "835600a2be276a18a4bebf0225d728f090f724f4c0acd591d066fa6f",
159+
},
160+
],
161+
}
162+
163+
assert NativeScript.from_dict(script_dict) == script_nofk
164+
165+
166+
def test_from_dict():
167+
168+
vk1 = VerificationKey.from_cbor(
169+
"58206443a101bdb948366fc87369336224595d36d8b0eee5602cba8b81a024e58473"
170+
)
171+
vk2 = VerificationKey.from_cbor(
172+
"58206443a101bdb948366fc87369336224595d36d8b0eee5602cba8b81a024e58475"
173+
)
174+
spk1 = ScriptPubkey(key_hash=vk1.hash())
175+
spk2 = ScriptPubkey(key_hash=vk2.hash())
176+
before = InvalidHereAfter(123456789)
177+
178+
script_all = ScriptAll([before, spk1, spk2])
179+
180+
script_all_dict = {
181+
"type": "all",
182+
"scripts": [
183+
{"type": "before", "slot": 123456789},
184+
{
185+
"type": "sig",
186+
"keyHash": "9139e5c0a42f0f2389634c3dd18dc621f5594c5ba825d9a8883c6627",
187+
},
188+
{
189+
"type": "sig",
190+
"keyHash": "835600a2be276a18a4bebf0225d728f090f724f4c0acd591d066fa6f",
191+
},
192+
],
193+
}
194+
195+
script_from_dict = NativeScript.from_dict(script_all_dict)
196+
197+
assert script_from_dict == script_all
198+
assert script_from_dict.to_dict() == script_all_dict

0 commit comments

Comments
 (0)