Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement explicit enums #116

Merged
merged 3 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions multiversx_sdk/abi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from multiversx_sdk.abi.biguint_value import BigUIntValue
from multiversx_sdk.abi.bool_value import BoolValue
from multiversx_sdk.abi.bytes_value import BytesValue
from multiversx_sdk.abi.code_metadata_value import CodeMetadataValue
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By mistake 🙈

Fixed.

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
Expand All @@ -33,8 +33,8 @@
"BigUIntValue",
"BoolValue",
"BytesValue",
"CodeMetadataValue",
"EnumValue",
"ExplicitEnumValue",
"Field",
"ListValue",
"OptionValue",
Expand Down
7 changes: 7 additions & 0 deletions multiversx_sdk/abi/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
42 changes: 42 additions & 0 deletions multiversx_sdk/abi/abi_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,27 +160,33 @@ 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():
kind = definition["type"]

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:
raise ValueError(f"Unsupported kind of custom type: {kind}")

return cls(
enums=enums,
explicit_enums=explicit_enums,
structs=structs
)

Expand Down Expand Up @@ -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,
Expand Down
19 changes: 17 additions & 2 deletions multiversx_sdk/abi/abi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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")
Expand Down
7 changes: 6 additions & 1 deletion multiversx_sdk/abi/enum_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -108,3 +108,8 @@ def __iter__(self):

for field in self.fields:
yield (field.name, field.value)


class _EnumPayload(SimpleNamespace):
def __int__(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way, people can convert an EnumValue to an int (quite useful).

return getattr(self, ENUM_DISCRIMINANT_FIELD_NAME)
4 changes: 4 additions & 0 deletions multiversx_sdk/abi/enum_value_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
11 changes: 11 additions & 0 deletions multiversx_sdk/abi/explicit_enum_value.py
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor Author

@andreibancioiu andreibancioiu Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we don't differentiate between different types of enums (as seen in ABI) if they have the same value / name. We'll brainstorm this a bit (could be perfectly fine in practice).

145 changes: 85 additions & 60 deletions multiversx_sdk/testutils/testdata/artificial.abi.json
Original file line number Diff line number Diff line change
@@ -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<multi<TokenIdentifier,u64,BigUint>>"
}
]
"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<u32, bytes, bool>"
"name": "blue",
"mutability": "readonly",
"inputs": [],
"outputs": [
{
"type": "optional<multi<TokenIdentifier,u64,BigUint>>"
}
]
},
{
"name": "yellow",
"mutability": "mutable",
"inputs": [
{
"name": "value",
"type": "multi<u32, bytes, bool>"
}
],
"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
}
]
}
]
]
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ allow-direct-references = true

[project]
name = "multiversx-sdk"
version = "0.15.0"
version = "0.16.0"
authors = [
{ name="MultiversX" },
]
Expand Down
Loading