Skip to content

Commit

Permalink
add support for encoding/decoding custom types
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Jan 17, 2025
1 parent 3c55525 commit 5a87be1
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 3 deletions.
127 changes: 126 additions & 1 deletion examples/v1.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -1933,6 +1933,131 @@
"parsed_event = events_parser.parse_event(event)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Encoding/Decoding custom types\n",
"\n",
"Whenever needed, the contract ABI can be used for manually encoding or decoding custom types.\n",
"\n",
"Let's encode a struct called `EsdtTokenPayment` (of [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract) into binary data."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"from multiversx_sdk.abi import Abi\n",
"\n",
"abi = Abi.load(Path(\"contracts/multisig-full.abi.json\"))\n",
"encoded = abi.encode_custom_type(\"EsdtTokenPayment\", [\"TEST-8b028f\", 0, 10000])\n",
"print(encoded)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, let's decode a struct using the ABI."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from multiversx_sdk.abi import Abi, AbiDefinition\n",
"\n",
"abi_definition = AbiDefinition.from_dict(\n",
" {\n",
" \"endpoints\": [],\n",
" \"events\": [],\n",
" \"types\": {\n",
" \"DepositEvent\": {\n",
" \"type\": \"struct\",\n",
" \"fields\": [\n",
" {\"name\": \"tx_nonce\", \"type\": \"u64\"},\n",
" {\"name\": \"opt_function\", \"type\": \"Option<bytes>\"},\n",
" {\"name\": \"opt_arguments\", \"type\": \"Option<List<bytes>>\"},\n",
" {\"name\": \"opt_gas_limit\", \"type\": \"Option<u64>\"},\n",
" ],\n",
" }\n",
" },\n",
" }\n",
")\n",
"abi = Abi(abi_definition)\n",
"\n",
"decoded_type = abi.decode_custom_type(name=\"DepositEvent\", data=bytes.fromhex(\"00000000000003db000000\"))\n",
"print(decoded_type)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you don't wish to use the ABI, there is another way to do it. First, let's encode a struct."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from multiversx_sdk.abi import Serializer, U64Value, StructValue, Field, StringValue, BigUIntValue\n",
"\n",
"struct = StructValue([\n",
" Field(name=\"token_identifier\", value=StringValue(\"TEST-8b028f\")),\n",
" Field(name=\"token_nonce\", value=U64Value()),\n",
" Field(name=\"amount\", value=BigUIntValue(10000)),\n",
"])\n",
"\n",
"serializer = Serializer()\n",
"serialized_struct = serializer.serialize([struct])\n",
"print(serialized_struct)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, let's decode a struct without using the ABI."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from multiversx_sdk.abi import Serializer, U64Value, OptionValue, BytesValue, ListValue, StructValue, Field\n",
"\n",
"tx_nonce = U64Value()\n",
"function = OptionValue(BytesValue())\n",
"arguments = OptionValue(ListValue([BytesValue()]))\n",
"gas_limit = OptionValue(U64Value())\n",
"\n",
"attributes = StructValue([\n",
" Field(\"tx_nonce\", tx_nonce),\n",
" Field(\"opt_function\", function),\n",
" Field(\"opt_arguments\", arguments),\n",
" Field(\"opt_gas_limit\", gas_limit)\n",
"])\n",
"\n",
"serializer = Serializer()\n",
"serializer.deserialize(\"00000000000003db000000\", [attributes])\n",
"\n",
"print(tx_nonce.get_payload())\n",
"print(function.get_payload())\n",
"print(arguments.get_payload())\n",
"print(gas_limit.get_payload())"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
20 changes: 19 additions & 1 deletion multiversx_sdk/abi/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
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.interface import IPayloadHolder, ISingleValue
from multiversx_sdk.abi.list_value import ListValue
from multiversx_sdk.abi.managed_decimal_signed_value import ManagedDecimalSignedValue
from multiversx_sdk.abi.managed_decimal_value import ManagedDecimalValue
Expand Down Expand Up @@ -250,6 +250,24 @@ def decode_event(self, event_name: str, topics: list[bytes], additional_data: li

return result

def encode_custom_type(self, name: str, values: list[Any]):
try:
custom_type: IPayloadHolder = self.custom_types_prototypes_by_name[name]
except KeyError:
raise Exception(f'Missing custom type! No custom type found for name: "{name}"')

custom_type.set_payload(values)
return self._serializer.serialize([custom_type])

def decode_custom_type(self, name: str, data: bytes) -> Any:
try:
custom_type: ISingleValue = self.custom_types_prototypes_by_name[name]
except KeyError:
raise Exception(f'Missing custom type! No custom type found for name: "{name}"')

custom_type.decode_top_level(data)
return custom_type.get_payload()

def _get_custom_type_prototype(self, type_name: str) -> Any:
type_prototype = self.custom_types_prototypes_by_name.get(type_name)

Expand Down
75 changes: 74 additions & 1 deletion multiversx_sdk/abi/abi_test.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import re
from decimal import Decimal
from pathlib import Path
from types import SimpleNamespace
from typing import Optional

import pytest

from multiversx_sdk.abi.abi import Abi
from multiversx_sdk.abi.abi_definition import AbiDefinition, ParameterDefinition
from multiversx_sdk.abi.address_value import AddressValue
from multiversx_sdk.abi.biguint_value import BigUIntValue
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.enum_value import EnumValue, _EnumPayload
from multiversx_sdk.abi.explicit_enum_value import ExplicitEnumValue
from multiversx_sdk.abi.fields import Field
from multiversx_sdk.abi.list_value import ListValue
Expand Down Expand Up @@ -408,3 +411,73 @@ def test_encode_decode_managed_decimals():

values = abi.decode_endpoint_output_parameters("foobar", [bytes.fromhex("0000000202bc00000002")])
assert values[0] == Decimal("7")


def test_decode_custom_struct():
abi_definition = AbiDefinition.from_dict(
{
"endpoints": [],
"events": [],
"types": {
"DepositEvent": {
"type": "struct",
"fields": [
{"name": "tx_nonce", "type": "u64"},
{"name": "opt_function", "type": "Option<bytes>"},
{"name": "opt_arguments", "type": "Option<List<bytes>>"},
{"name": "opt_gas_limit", "type": "Option<u64>"},
],
}
},
}
)
abi = Abi(abi_definition)

with pytest.raises(Exception, match=re.escape('Missing custom type! No custom type found for name: "customType"')):
abi.decode_custom_type("customType", b"")

decoded_type = abi.decode_custom_type(name="DepositEvent", data=bytes.fromhex("00000000000003db000000"))
assert decoded_type == SimpleNamespace(
tx_nonce=987,
opt_function=None,
opt_arguments=None,
opt_gas_limit=None,
)


def test_decode_custom_enum():
abi = Abi.load(testdata / "multisig-full.abi.json")

decoded_type = abi.decode_custom_type(
name="Action",
data=bytes.fromhex(
"0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107"
),
)

expected_output = _EnumPayload()
setattr(
expected_output,
"0",
SimpleNamespace(
{
"to": bytes.fromhex("00000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1"),
"egld_amount": 42,
"opt_gas_limit": None,
"endpoint_name": b"add",
"arguments": [bytes([0x07])],
},
),
)
setattr(expected_output, "__discriminant__", 5)
assert decoded_type == expected_output


def test_encode_custom_struct():
abi = Abi.load(testdata / "multisig-full.abi.json")

with pytest.raises(Exception, match=re.escape('Missing custom type! No custom type found for name: "customType"')):
abi.encode_custom_type("customType", [])

encoded = abi.encode_custom_type("EsdtTokenPayment", ["TEST-8b028f", 0, 10000])
assert encoded == "encode_custom_type"

0 comments on commit 5a87be1

Please sign in to comment.