diff --git a/pycardano/certificate.py b/pycardano/certificate.py index 11b8892a..c7620dd2 100644 --- a/pycardano/certificate.py +++ b/pycardano/certificate.py @@ -1,8 +1,13 @@ from dataclasses import dataclass, field -from typing import Optional, Union +from typing import Optional, Union, Type +from pycardano.exception import DeserializeException from pycardano.hash import PoolKeyHash, ScriptHash, VerificationKeyHash -from pycardano.serialization import ArrayCBORSerializable +from pycardano.serialization import ( + ArrayCBORSerializable, + ArrayBase, + limit_primitive_type, +) __all__ = [ "Certificate", @@ -25,6 +30,20 @@ def __post_init__(self): else: self._CODE = 1 + @classmethod + @limit_primitive_type(list, tuple) + def from_primitive( + cls: Type[ArrayBase], values: Union[list, tuple] + ) -> "StakeCredential": + if len(values) != 2: + raise ValueError(f"Expected 2 values, got {len(values)}") + if values[0] == 0: + return StakeCredential(VerificationKeyHash.from_primitive(values[1])) + elif values[0] == 1: + return StakeCredential(ScriptHash.from_primitive(values[1])) + else: + raise ValueError(f"Unknown code: {values[0]}") + @dataclass(repr=False) class StakeRegistration(ArrayCBORSerializable): @@ -32,6 +51,17 @@ class StakeRegistration(ArrayCBORSerializable): stake_credential: StakeCredential + @classmethod + @limit_primitive_type(list, tuple) + def from_primitive( + cls: Type[ArrayBase], values: Union[list, tuple] + ) -> "StakeRegistration": + if len(values) != 2: + raise DeserializeException(f"Expected 2 values, got {len(values)}") + if values[0] != 0: + raise DeserializeException(f"Expected 0, got {values[0]}") + return StakeRegistration(StakeCredential.from_primitive(values[1])) + @dataclass(repr=False) class StakeDeregistration(ArrayCBORSerializable): @@ -39,6 +69,17 @@ class StakeDeregistration(ArrayCBORSerializable): stake_credential: StakeCredential + @classmethod + @limit_primitive_type(list, tuple) + def from_primitive( + cls: Type[ArrayBase], values: Union[list, tuple] + ) -> "StakeDeregistration": + if len(values) != 2: + raise DeserializeException(f"Expected 2 values, got {len(values)}") + if values[0] != 1: + raise DeserializeException(f"Expected 1, got {values[0]}") + return StakeDeregistration(StakeCredential.from_primitive(values[1])) + @dataclass(repr=False) class StakeDelegation(ArrayCBORSerializable): @@ -48,5 +89,19 @@ class StakeDelegation(ArrayCBORSerializable): pool_keyhash: PoolKeyHash + @classmethod + @limit_primitive_type(list, tuple) + def from_primitive( + cls: Type[ArrayBase], values: Union[list, tuple] + ) -> "StakeDelegation": + if len(values) != 3: + raise DeserializeException(f"Expected 3 values, got {len(values)}") + if values[0] != 2: + raise DeserializeException(f"Expected 2, got {values[0]}") + return StakeDelegation( + StakeCredential.from_primitive(values[1]), + PoolKeyHash.from_primitive(values[2]), + ) + Certificate = Union[StakeRegistration, StakeDeregistration, StakeDelegation] diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index 987921d2..153bafac 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -316,3 +316,13 @@ def test_deserialize_empty_value(): transaction.transaction_body.outputs[0].amount.multi_asset == pycardano.MultiAsset() ), "Invalid deserialization of multi asset" + + +def test_certificate_transaction_deserialize(): + """ + This should not crash even though there is an empty value in the transaction + """ + pycardano.Transaction.from_cbor( + "84a6008282582060a6ad87d0bb0a1f6153d908c003a63a2e29d80d64ea243e8a7c322a322b62a700825820c8337cabe7956bf5e903f2591b7cbe1e50bc4dc3402aadd075d4b5eded9ee70d0001818258393025094314c7b532cc1d35f8acd9f7eb307ac0cf49e012e17fd974a03a0f09a6c0f72ebc76d0db9bad8667a24f58734a8f7a8b2839ec1cc8461a00bc5a50021a0002d249031a03178183048282008201581c0f09a6c0f72ebc76d0db9bad8667a24f58734a8f7a8b2839ec1cc84683028201581c0f09a6c0f72ebc76d0db9bad8667a24f58734a8f7a8b2839ec1cc846581c376a90568c7ac62662147f919fc5a919a1ad9ed853e056d244f74b630800a2008382582047cfd2ef0b24eb9316127bb7b0b8d6befafdfd56f37c2892e6098fdc4539a7f7584039abdea2179a593027d5f8249356865cd6ab184063c0196daa4051d8e75ee2ea36648aa190f133e61220798cb8cb0a6e573f339e4ac9938f712577bc38e381028258206864b22adc8d2aa625bd28e7165459bce9057b712026269e53ce372117f6762658402c84d426f506bd76fd35fa4ca7edbb4bea5b2ca0d585894dacf6470bf07117fe61b1c5c137188b93913ca0df7dd8e0ef8a29c75c1a168f370e3ea5120909c804825820d00dcafc4918495980b0768361d051f3ece94cccb887a9f22b9480f9cda0bae75840a257e022f60953bb293af406cc5b399d283a9daf6e787baade74e603476f8474302217d33573dc7029689ac1cc55e5b85fadb39e7ed7104f17949d738417950801838201818200581cf08a3f7f85aea5f58540c6705eefd580efb67e03597d9154b832ac4e8201818200581c74005c045440ef7604f3ebe8bd8a90660024459754eec6a13656a8ad8201818200581c6f3f457a6e4a8b2b23aac8c9b7a010b3631b2cf990ef444a9ef4142ff5f6" + ) + # TODO add test for presence of certificate