Skip to content

Commit e28f80a

Browse files
authored
Merge pull request #155 from SinaKhalili/sina/add-types-coder
Add types coder
2 parents 4799c1a + 2bfd2f9 commit e28f80a

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

src/anchorpy/coder/coder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from anchorpy.coder.accounts import AccountsCoder
55
from anchorpy.coder.event import EventCoder
66
from anchorpy.coder.instruction import InstructionCoder
7+
from anchorpy.coder.types import TypesCoder
78

89

910
class Coder:
@@ -18,3 +19,4 @@ def __init__(self, idl: Idl):
1819
self.instruction: InstructionCoder = InstructionCoder(idl)
1920
self.accounts: AccountsCoder = AccountsCoder(idl)
2021
self.events: EventCoder = EventCoder(idl)
22+
self.types: TypesCoder = TypesCoder(idl)

src/anchorpy/coder/types.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""The TypesCoder class is for encoding and decoding user-defined types."""
2+
3+
from typing import Any, Dict
4+
5+
from anchorpy_core.idl import Idl
6+
from construct import Construct, Container
7+
8+
from anchorpy.coder.idl import _typedef_layout_without_field_name
9+
10+
11+
class TypesCoder:
12+
"""Encodes and decodes user-defined types in Anchor programs."""
13+
14+
def __init__(self, idl: Idl) -> None:
15+
"""Initialize the TypesCoder.
16+
17+
Args:
18+
idl: The parsed IDL object.
19+
"""
20+
self.idl = idl
21+
self.types_layouts: Dict[str, Construct] = {}
22+
23+
self.filtered_types = []
24+
if idl.types:
25+
self.filtered_types = [
26+
ty for ty in idl.types if not getattr(ty, "generics", None)
27+
]
28+
29+
def _get_layout(self, name: str) -> Construct:
30+
"""Get or create a layout for a given type name.
31+
32+
Args:
33+
name: The name of the type.
34+
35+
Returns:
36+
The construct layout for the type.
37+
38+
Raises:
39+
ValueError: If the type is not found.
40+
"""
41+
if name in self.types_layouts:
42+
return self.types_layouts[name]
43+
44+
type_defs = [t for t in self.filtered_types if t.name == name]
45+
if not type_defs:
46+
raise ValueError(f"Unknown type: {name}")
47+
48+
layout = _typedef_layout_without_field_name(type_defs[0], self.idl.types)
49+
self.types_layouts[name] = layout
50+
return layout
51+
52+
def encode(self, name: str, data: Any) -> bytes:
53+
"""Encode a user-defined type.
54+
55+
Args:
56+
name: The name of the type.
57+
data: The data to encode.
58+
59+
Returns:
60+
The encoded data.
61+
62+
Raises:
63+
ValueError: If the type is not found.
64+
"""
65+
layout = self._get_layout(name)
66+
return layout.build(data)
67+
68+
def decode(self, name: str, buffer: bytes) -> Container[Any]:
69+
"""Decode a user-defined type.
70+
71+
Args:
72+
name: The name of the type.
73+
buffer: The buffer to decode.
74+
75+
Returns:
76+
The decoded data.
77+
78+
Raises:
79+
ValueError: If the type is not found.
80+
"""
81+
layout = self._get_layout(name)
82+
return layout.parse(buffer)

tests/unit/test_types_coder.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import json
2+
3+
import pytest
4+
from anchorpy import Coder
5+
from anchorpy_core.idl import Idl
6+
7+
8+
@pytest.mark.unit
9+
def test_can_encode_and_decode_user_defined_types():
10+
"""Test that the TypesCoder can encode and decode user-defined types."""
11+
idl_json = {
12+
"version": "0.0.0",
13+
"name": "basic_0",
14+
"address": "Test111111111111111111111111111111111111111",
15+
"instructions": [
16+
{
17+
"name": "initialize",
18+
"accounts": [],
19+
"args": [],
20+
"discriminator": [],
21+
},
22+
],
23+
"types": [
24+
{
25+
"name": "MintInfo",
26+
"type": {
27+
"kind": "struct",
28+
"fields": [
29+
{
30+
"name": "minted",
31+
"type": "bool",
32+
},
33+
{
34+
"name": "metadataUrl",
35+
"type": "string",
36+
},
37+
],
38+
},
39+
},
40+
],
41+
}
42+
idl = Idl.from_json(json.dumps(idl_json))
43+
coder = Coder(idl)
44+
45+
mint_info = {
46+
"minted": True,
47+
"metadata_url": "hello",
48+
}
49+
encoded = coder.types.encode("MintInfo", mint_info)
50+
decoded = coder.types.decode("MintInfo", encoded)
51+
52+
# Compare decoded values with original
53+
assert decoded.minted == mint_info["minted"]
54+
assert decoded.metadata_url == mint_info["metadata_url"]
55+
56+
57+
@pytest.mark.unit
58+
def test_can_encode_and_decode_large_integers():
59+
"""Test that the TypesCoder can encode and decode 128-bit integers."""
60+
idl_json = {
61+
"version": "0.0.0",
62+
"name": "basic_0",
63+
"address": "Test111111111111111111111111111111111111111",
64+
"instructions": [
65+
{
66+
"name": "initialize",
67+
"accounts": [],
68+
"args": [],
69+
"discriminator": [],
70+
},
71+
],
72+
"types": [
73+
{
74+
"name": "IntegerTest",
75+
"type": {
76+
"kind": "struct",
77+
"fields": [
78+
{
79+
"name": "unsigned",
80+
"type": "u128",
81+
},
82+
{
83+
"name": "signed",
84+
"type": "i128",
85+
},
86+
],
87+
},
88+
},
89+
],
90+
}
91+
idl = Idl.from_json(json.dumps(idl_json))
92+
coder = Coder(idl)
93+
94+
integer_test = {
95+
"unsigned": 2588012355,
96+
"signed": -93842345,
97+
}
98+
encoded = coder.types.encode("IntegerTest", integer_test)
99+
decoded = coder.types.decode("IntegerTest", encoded)
100+
101+
assert decoded.unsigned == integer_test["unsigned"]
102+
assert decoded.signed == integer_test["signed"]

0 commit comments

Comments
 (0)