diff --git a/multiversx_sdk/abi/__init__.py b/multiversx_sdk/abi/__init__.py index 321ff4fb..d3d326bd 100644 --- a/multiversx_sdk/abi/__init__.py +++ b/multiversx_sdk/abi/__init__.py @@ -8,6 +8,7 @@ from multiversx_sdk.abi.bytes_value import BytesValue from multiversx_sdk.abi.code_metadata_value import CodeMetadataValue from multiversx_sdk.abi.enum_value import EnumValue +from multiversx_sdk.abi.explicit_enum_value import ExplicitEnumValue from multiversx_sdk.abi.fields import Field from multiversx_sdk.abi.list_value import ListValue from multiversx_sdk.abi.multi_value import MultiValue @@ -35,6 +36,7 @@ "BytesValue", "CodeMetadataValue", "EnumValue", + "ExplicitEnumValue", "Field", "ListValue", "OptionValue", diff --git a/multiversx_sdk/abi/abi.py b/multiversx_sdk/abi/abi.py index 49a480a4..7fd6c764 100644 --- a/multiversx_sdk/abi/abi.py +++ b/multiversx_sdk/abi/abi.py @@ -17,6 +17,7 @@ from multiversx_sdk.abi.code_metadata_value import CodeMetadataValue from multiversx_sdk.abi.counted_variadic_values import CountedVariadicValues from multiversx_sdk.abi.enum_value import EnumValue +from multiversx_sdk.abi.explicit_enum_value import ExplicitEnumValue from multiversx_sdk.abi.fields import Field from multiversx_sdk.abi.interface import IPayloadHolder from multiversx_sdk.abi.list_value import ListValue @@ -85,6 +86,9 @@ def _create_custom_type_prototype(self, name: str) -> Any: if name in self.definition.types.enums: definition = self.definition.types.enums[name] return self._create_enum_prototype(definition) + if name in self.definition.types.explicit_enums: + definition = self.definition.types.explicit_enums[name] + return self._create_explicit_enum_prototype() if name in self.definition.types.structs: definition = self.definition.types.structs[name] return self._create_struct_prototype(definition) @@ -94,6 +98,9 @@ def _create_custom_type_prototype(self, name: str) -> Any: def _create_enum_prototype(self, enum_definition: EnumDefinition) -> Any: return EnumValue(fields_provider=lambda discriminant: self._provide_fields_for_enum_prototype(discriminant, enum_definition)) + def _create_explicit_enum_prototype(self) -> Any: + return ExplicitEnumValue() + def _provide_fields_for_enum_prototype(self, discriminant: int, enum_definition: EnumDefinition) -> List[Field]: for variant in enum_definition.variants: if variant.discriminant != discriminant: diff --git a/multiversx_sdk/abi/abi_definition.py b/multiversx_sdk/abi/abi_definition.py index 0aaf5b3c..303585d3 100644 --- a/multiversx_sdk/abi/abi_definition.py +++ b/multiversx_sdk/abi/abi_definition.py @@ -160,13 +160,16 @@ def __eq__(self, value: object) -> bool: class TypesDefinitions: def __init__(self, enums: List["EnumDefinition"], + explicit_enums: List["ExplicitEnumDefinition"], structs: List["StructDefinition"]) -> None: self.enums: Dict[str, EnumDefinition] = {enum.name: enum for enum in enums} + self.explicit_enums: Dict[str, ExplicitEnumDefinition] = {enum.name: enum for enum in explicit_enums} self.structs: Dict[str, StructDefinition] = {struct.name: struct for struct in structs} @classmethod def from_dict(cls, data: Dict[str, Any]) -> "TypesDefinitions": enums: List[EnumDefinition] = [] + explicit_enums: List[ExplicitEnumDefinition] = [] structs: List[StructDefinition] = [] for name, definition in data.items(): @@ -174,6 +177,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "TypesDefinitions": if kind == "enum": enums.append(EnumDefinition.from_dict(name, definition)) + elif kind == "explicit-enum": + explicit_enums.append(ExplicitEnumDefinition.from_dict(name, definition)) elif kind == "struct": structs.append(StructDefinition.from_dict(name, definition)) else: @@ -181,6 +186,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "TypesDefinitions": return cls( enums=enums, + explicit_enums=explicit_enums, structs=structs ) @@ -228,6 +234,42 @@ def __repr__(self): return f"EnumVariantDefinition(name={self.name}, discriminant={self.discriminant})" +class ExplicitEnumDefinition: + def __init__(self, + name: str, + variants: List["ExplicitEnumVariantDefinition"]) -> None: + self.name = name + self.variants = variants + + @classmethod + def from_dict(cls, name: str, data: Dict[str, Any]) -> "ExplicitEnumDefinition": + variants = [ExplicitEnumVariantDefinition.from_dict(item) for item in data["variants"]] + + return cls( + name=name, + variants=variants + ) + + def __repr__(self): + return f"ExplicitEnumDefinition(name={self.name})" + + +class ExplicitEnumVariantDefinition: + def __init__(self, name: str) -> None: + self.name = name + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "ExplicitEnumVariantDefinition": + fields = [FieldDefinition.from_dict(item) for item in data.get("fields", [])] + + return cls( + name=data.get("name", "") + ) + + def __repr__(self): + return f"ExplicitEnumVariantDefinition(name={self.name})" + + class StructDefinition: def __init__(self, name: str, diff --git a/multiversx_sdk/abi/abi_test.py b/multiversx_sdk/abi/abi_test.py index 0121726f..0996725f 100644 --- a/multiversx_sdk/abi/abi_test.py +++ b/multiversx_sdk/abi/abi_test.py @@ -9,6 +9,7 @@ from multiversx_sdk.abi.bytes_value import BytesValue from multiversx_sdk.abi.counted_variadic_values import CountedVariadicValues from multiversx_sdk.abi.enum_value import EnumValue +from multiversx_sdk.abi.explicit_enum_value import ExplicitEnumValue from multiversx_sdk.abi.fields import Field from multiversx_sdk.abi.list_value import ListValue from multiversx_sdk.abi.option_value import OptionValue @@ -21,7 +22,7 @@ testdata = Path(__file__).parent.parent / "testutils" / "testdata" -def test_abi(): +def test_abi_adder(): abi = Abi.load(testdata / "adder.abi.json") assert abi.definition.constructor.name == "constructor" @@ -59,9 +60,14 @@ def test_abi(): assert abi.endpoints_prototypes_by_name["add"].output_parameters == [] -def test_abi_events(): +def test_abi_artificial(): abi = Abi.load(testdata / "artificial.abi.json") + assert len(abi.definition.types.explicit_enums) == 1 + assert "OperationCompletionStatus" in abi.definition.types.explicit_enums + assert abi.definition.endpoints[3].outputs[0].type == "OperationCompletionStatus" + assert abi.endpoints_prototypes_by_name["green"].output_parameters[0] == ExplicitEnumValue() + assert len(abi.definition.events) == 1 assert abi.events_prototypes_by_name["firstEvent"].fields[0].value == BigUIntValue() @@ -110,6 +116,15 @@ def test_decode_endpoint_output_parameters_artificial_contract(): assert decoded_values == [["UTK-2f80e9", 0, 1000000000000000000]] + decoded_values = abi.decode_endpoint_output_parameters( + endpoint_name="green", + encoded_values=[ + "completed".encode(), + ] + ) + + assert decoded_values == ["completed"] + def test_encode_endpoint_input_parameters_multisig_propose_batch(): abi = Abi.load(testdata / "multisig-full.abi.json") diff --git a/multiversx_sdk/abi/enum_value.py b/multiversx_sdk/abi/enum_value.py index 7b64bc1a..1f7fc449 100644 --- a/multiversx_sdk/abi/enum_value.py +++ b/multiversx_sdk/abi/enum_value.py @@ -87,7 +87,7 @@ def set_payload(self, value: Any): raise ValueError("cannot set payload for enum (should be either a dictionary or a list)") def get_payload(self) -> Any: - obj = SimpleNamespace() + obj = _EnumPayload() for field in self.fields: setattr(obj, field.name, field.get_payload()) @@ -108,3 +108,8 @@ def __iter__(self): for field in self.fields: yield (field.name, field.value) + + +class _EnumPayload(SimpleNamespace): + def __int__(self): + return getattr(self, ENUM_DISCRIMINANT_FIELD_NAME) diff --git a/multiversx_sdk/abi/enum_value_test.py b/multiversx_sdk/abi/enum_value_test.py index d1dbe380..3058a5d7 100644 --- a/multiversx_sdk/abi/enum_value_test.py +++ b/multiversx_sdk/abi/enum_value_test.py @@ -60,6 +60,7 @@ def provide_fields(discriminant: int) -> List[Field]: assert value.discriminant == 41 assert value.fields == [Field("a", U32Value(1)), Field("b", BigUIntValue(2))] assert value.get_payload() == SimpleNamespace(__discriminant__=41, a=1, b=2) + assert int(value.get_payload()) == 41 class Payload: def __init__(self, c: int, d: int): @@ -72,15 +73,18 @@ def __init__(self, c: int, d: int): assert value.discriminant == 42 assert value.fields == [Field("c", U32Value(3)), Field("d", BigUIntValue(4))] assert value.get_payload() == SimpleNamespace(__discriminant__=42, c=3, d=4) + assert int(value.get_payload()) == 42 # Then, from dictionary value.set_payload({"__discriminant__": 43, "e": 5, "f": 6}) assert value.discriminant == 43 assert value.fields == [Field("e", U32Value(5)), Field("f", BigUIntValue(6))] assert value.get_payload() == SimpleNamespace(__discriminant__=43, e=5, f=6) + assert int(value.get_payload()) == 43 # Finally, from list (first element is the discriminant) value.set_payload([44, 7, 8]) assert value.discriminant == 44 assert value.fields == [Field("g", U32Value(7)), Field("h", BigUIntValue(8))] assert value.get_payload() == SimpleNamespace(__discriminant__=44, g=7, h=8) + assert int(value.get_payload()) == 44 diff --git a/multiversx_sdk/abi/explicit_enum_value.py b/multiversx_sdk/abi/explicit_enum_value.py new file mode 100644 index 00000000..df8d6e23 --- /dev/null +++ b/multiversx_sdk/abi/explicit_enum_value.py @@ -0,0 +1,11 @@ +from typing import Any + +from multiversx_sdk.abi.string_value import StringValue + + +class ExplicitEnumValue(StringValue): + def __init__(self, value: str = "") -> None: + self.value = value + + def __eq__(self, other: Any) -> bool: + return isinstance(other, ExplicitEnumValue) and self.value == other.value diff --git a/multiversx_sdk/testutils/testdata/artificial.abi.json b/multiversx_sdk/testutils/testdata/artificial.abi.json index af17c4d2..715a53f4 100644 --- a/multiversx_sdk/testutils/testdata/artificial.abi.json +++ b/multiversx_sdk/testutils/testdata/artificial.abi.json @@ -1,68 +1,93 @@ { - "name": "Artificial", - "constructor": { - "inputs": [ - { - "name": "a", - "type": "utf-8 string" - } - ], - "outputs": [] - }, - "upgradeConstructor": { - "inputs": [ - { - "name": "a", - "type": "u8" - } - ], - "outputs": [] - }, - "endpoints": [ - { - "name": "blue", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "optional>" - } - ] + "name": "Artificial", + "constructor": { + "inputs": [ + { + "name": "a", + "type": "utf-8 string" + } + ], + "outputs": [] + }, + "upgradeConstructor": { + "inputs": [ + { + "name": "a", + "type": "u8" + } + ], + "outputs": [] }, - { - "name": "yellow", - "mutability": "mutable", - "inputs": [ + "endpoints": [ { - "name": "value", - "type": "multi" + "name": "blue", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "optional>" + } + ] + }, + { + "name": "yellow", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "multi" + } + ], + "outputs": [] + }, + { + "name": "orange", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "EgldOrEsdtTokenIdentifier" + } + ], + "outputs": [] + }, + { + "name": "green", + "inputs": [], + "outputs": [ + { + "type": "OperationCompletionStatus" + } + ] + } + ], + "types": { + "OperationCompletionStatus": { + "type": "explicit-enum", + "variants": [ + { + "docs": ["indicates that operation was completed"], + "name": "completed" + }, + { + "docs": [ + "indicates that operation was interrupted prematurely, due to low gas" + ], + "name": "interrupted" + } + ] } - ], - "outputs": [] }, - { - "name": "orange", - "mutability": "mutable", - "inputs": [ + "events": [ { - "name": "value", - "type": "EgldOrEsdtTokenIdentifier" + "identifier": "firstEvent", + "inputs": [ + { + "name": "result", + "type": "BigUint", + "indexed": true + } + ] } - ], - "outputs": [] - } - ], - "types": {}, - "events": [ - { - "identifier": "firstEvent", - "inputs": [ - { - "name": "result", - "type": "BigUint", - "indexed": true - } - ] - } - ] + ] } diff --git a/pyproject.toml b/pyproject.toml index 8da78b39..731a6b72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ allow-direct-references = true [project] name = "multiversx-sdk" -version = "0.15.0" +version = "0.16.0" authors = [ { name="MultiversX" }, ]