Skip to content

Commit e8616b9

Browse files
authored
Merge pull request #30 from icon-project/feat/abi
feat(abi): implement Clarity ABI unmarshalling
2 parents 659e286 + 1c1c3cc commit e8616b9

File tree

5 files changed

+2352
-8
lines changed

5 files changed

+2352
-8
lines changed

pkg/abi/abi.go

+338
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
package abi
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/icon-project/stacks-go-sdk/pkg/clarity"
10+
)
11+
12+
type ABI struct {
13+
Maps []Map `json:"maps"`
14+
Epoch string `json:"epoch"`
15+
Functions []Function `json:"functions"`
16+
Variables []Variable `json:"variables"`
17+
ClarityVersion string `json:"clarity_version"`
18+
FungibleTokens []interface{} `json:"fungible_tokens"`
19+
NonFungibleTokens []interface{} `json:"non_fungible_tokens"`
20+
}
21+
22+
type Map struct {
23+
Key ClarityTypeDescriptor `json:"key"`
24+
Name string `json:"name"`
25+
Value ClarityTypeDescriptor `json:"value"`
26+
}
27+
28+
type Function struct {
29+
Args []Argument `json:"args"`
30+
Name string `json:"name"`
31+
Access string `json:"access"`
32+
Outputs OutputDescriptor `json:"outputs"`
33+
}
34+
35+
type Argument struct {
36+
Name string `json:"name"`
37+
Type ClarityTypeDescriptor `json:"type"`
38+
}
39+
40+
type OutputDescriptor struct {
41+
Type ClarityTypeDescriptor `json:"type"`
42+
}
43+
44+
type Variable struct {
45+
Name string `json:"name"`
46+
Type ClarityTypeDescriptor `json:"type"`
47+
Access string `json:"access"`
48+
}
49+
50+
type ClarityTypeDescriptor struct {
51+
Raw json.RawMessage `json:"-"`
52+
53+
StringASCII *StringASCIIDescriptor `json:"string-ascii,omitempty"`
54+
StringUTF8 *StringUTF8Descriptor `json:"string-utf8,omitempty"`
55+
Uint128 *struct{} `json:"uint128,omitempty"`
56+
Int128 *struct{} `json:"int128,omitempty"`
57+
Bool *struct{} `json:"bool,omitempty"`
58+
Buffer *BufferDescriptor `json:"buffer,omitempty"`
59+
Optional *OptionalDescriptor `json:"optional,omitempty"`
60+
List *ListDescriptor `json:"list,omitempty"`
61+
Tuple *TupleDescriptor `json:"tuple,omitempty"`
62+
Response *ResponseDescriptor `json:"response,omitempty"`
63+
Principal *struct{} `json:"principal,omitempty"`
64+
TraitReference *struct{} `json:"trait_reference,omitempty"`
65+
None *struct{} `json:"none,omitempty"`
66+
}
67+
68+
type StringASCIIDescriptor struct {
69+
Length int `json:"length"`
70+
}
71+
72+
type StringUTF8Descriptor struct {
73+
Length int `json:"length"`
74+
}
75+
76+
type BufferDescriptor struct {
77+
Length int `json:"length"`
78+
}
79+
80+
type OptionalDescriptor struct {
81+
Type ClarityTypeDescriptor `json:"type"`
82+
}
83+
84+
type ListDescriptor struct {
85+
Type ClarityTypeDescriptor `json:"type"`
86+
Length int `json:"length"`
87+
}
88+
89+
type TupleDescriptor struct {
90+
Fields []TupleField `json:"tuple"`
91+
}
92+
93+
type TupleField struct {
94+
Name string `json:"name"`
95+
Type ClarityTypeDescriptor `json:"type"`
96+
}
97+
98+
type TraitReferenceDescriptor struct {
99+
}
100+
101+
type ResponseDescriptor struct {
102+
Ok *ClarityTypeDescriptor `json:"ok,omitempty"`
103+
Error *ClarityTypeDescriptor `json:"error,omitempty"`
104+
}
105+
106+
func (o *OptionalDescriptor) UnmarshalJSON(data []byte) error {
107+
var typeStr string
108+
if err := json.Unmarshal(data, &typeStr); err == nil {
109+
var desc ClarityTypeDescriptor
110+
if err := json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, typeStr)), &desc); err != nil {
111+
return fmt.Errorf("failed to unmarshal optional type string '%s': %v", typeStr, err)
112+
}
113+
o.Type = desc
114+
return nil
115+
}
116+
117+
var temp struct {
118+
Type ClarityTypeDescriptor `json:"type"`
119+
}
120+
if err := json.Unmarshal(data, &temp); err != nil {
121+
return fmt.Errorf("failed to unmarshal optional as object: %v", err)
122+
}
123+
o.Type = temp.Type
124+
return nil
125+
}
126+
127+
func (c *ClarityTypeDescriptor) UnmarshalJSON(data []byte) error {
128+
c.Raw = data
129+
130+
var typeStr string
131+
if err := json.Unmarshal(data, &typeStr); err == nil {
132+
switch typeStr {
133+
case "uint128":
134+
c.Uint128 = &struct{}{}
135+
case "int128":
136+
c.Int128 = &struct{}{}
137+
case "bool":
138+
c.Bool = &struct{}{}
139+
case "principal":
140+
c.Principal = &struct{}{}
141+
case "trait_reference":
142+
c.TraitReference = &struct{}{}
143+
case "none":
144+
c.None = &struct{}{}
145+
default:
146+
return fmt.Errorf("unknown Clarity type string: %s", typeStr)
147+
}
148+
return nil
149+
}
150+
151+
var tempMap map[string]json.RawMessage
152+
if err := json.Unmarshal(data, &tempMap); err != nil {
153+
return fmt.Errorf("ClarityTypeDescriptor should be a JSON object: %v", err)
154+
}
155+
156+
for key, value := range tempMap {
157+
switch key {
158+
case "string-ascii":
159+
var desc StringASCIIDescriptor
160+
if err := json.Unmarshal(value, &desc); err != nil {
161+
return fmt.Errorf("failed to unmarshal string-ascii: %v", err)
162+
}
163+
c.StringASCII = &desc
164+
case "string-utf8":
165+
var desc StringUTF8Descriptor
166+
if err := json.Unmarshal(value, &desc); err != nil {
167+
return fmt.Errorf("failed to unmarshal string-utf8: %v", err)
168+
}
169+
c.StringUTF8 = &desc
170+
case "uint128":
171+
c.Uint128 = &struct{}{}
172+
case "int128":
173+
c.Int128 = &struct{}{}
174+
case "bool":
175+
c.Bool = &struct{}{}
176+
case "buffer":
177+
var desc BufferDescriptor
178+
if err := json.Unmarshal(value, &desc); err != nil {
179+
return fmt.Errorf("failed to unmarshal buffer: %v", err)
180+
}
181+
c.Buffer = &desc
182+
case "optional":
183+
var desc OptionalDescriptor
184+
if err := json.Unmarshal(value, &desc); err != nil {
185+
return fmt.Errorf("failed to unmarshal optional: %v", err)
186+
}
187+
c.Optional = &desc
188+
case "list":
189+
var desc ListDescriptor
190+
if err := json.Unmarshal(value, &desc); err != nil {
191+
return fmt.Errorf("failed to unmarshal list: %v", err)
192+
}
193+
c.List = &desc
194+
case "tuple":
195+
var desc TupleDescriptor
196+
if err := json.Unmarshal(data, &desc); err != nil {
197+
return fmt.Errorf("failed to unmarshal tuple: %v", err)
198+
}
199+
c.Tuple = &desc
200+
case "response":
201+
var desc ResponseDescriptor
202+
if err := json.Unmarshal(value, &desc); err != nil {
203+
return fmt.Errorf("failed to unmarshal response: %v", err)
204+
}
205+
c.Response = &desc
206+
case "principal":
207+
c.Principal = &struct{}{}
208+
case "trait_reference":
209+
c.TraitReference = &struct{}{}
210+
default:
211+
return fmt.Errorf("unknown Clarity type key: %s", key)
212+
}
213+
}
214+
215+
return nil
216+
}
217+
218+
func ClarityTypeToClarityValue(descriptor ClarityTypeDescriptor, value interface{}) (clarity.ClarityValue, error) {
219+
switch {
220+
case descriptor.Uint128 != nil:
221+
valStr, ok := value.(string)
222+
if !ok {
223+
return nil, fmt.Errorf("expected string for uint128 value")
224+
}
225+
return clarity.NewUInt(valStr)
226+
case descriptor.Int128 != nil:
227+
valStr, ok := value.(string)
228+
if !ok {
229+
return nil, fmt.Errorf("expected string for int128 value")
230+
}
231+
return clarity.NewInt(valStr)
232+
case descriptor.Bool != nil:
233+
valBool, ok := value.(bool)
234+
if !ok {
235+
return nil, fmt.Errorf("expected bool for bool value")
236+
}
237+
return clarity.NewBool(valBool), nil
238+
case descriptor.Buffer != nil:
239+
valBytes, ok := value.(string)
240+
if !ok {
241+
return nil, fmt.Errorf("expected string for buffer value")
242+
}
243+
decoded, err := hex.DecodeString(strings.TrimPrefix(valBytes, "0x"))
244+
if err != nil {
245+
return nil, fmt.Errorf("invalid hex string for buffer: %v", err)
246+
}
247+
return clarity.NewBuffer(decoded), nil
248+
case descriptor.StringASCII != nil:
249+
valStr, ok := value.(string)
250+
if !ok {
251+
return nil, fmt.Errorf("expected string for string-ascii value")
252+
}
253+
return clarity.NewStringASCII(valStr)
254+
case descriptor.StringUTF8 != nil:
255+
valStr, ok := value.(string)
256+
if !ok {
257+
return nil, fmt.Errorf("expected string for string-utf8 value")
258+
}
259+
return clarity.NewStringUTF8(valStr)
260+
case descriptor.Optional != nil:
261+
if value == nil {
262+
return clarity.NewOptionNone(), nil
263+
}
264+
someValue, err := ClarityTypeToClarityValue(descriptor.Optional.Type, value)
265+
if err != nil {
266+
return nil, fmt.Errorf("failed to create OptionSome: %v", err)
267+
}
268+
return clarity.NewOptionSome(someValue), nil
269+
case descriptor.List != nil:
270+
listItems, ok := value.([]interface{})
271+
if !ok {
272+
return nil, fmt.Errorf("expected slice for list value")
273+
}
274+
clarityList := make([]clarity.ClarityValue, 0, len(listItems))
275+
for _, item := range listItems {
276+
clarityVal, err := ClarityTypeToClarityValue(descriptor.List.Type, item)
277+
if err != nil {
278+
return nil, fmt.Errorf("failed to create list item: %v", err)
279+
}
280+
clarityList = append(clarityList, clarityVal)
281+
}
282+
return clarity.NewList(clarityList), nil
283+
case descriptor.Tuple != nil:
284+
tupleMap, ok := value.(map[string]interface{})
285+
if !ok {
286+
return nil, fmt.Errorf("expected map for tuple value")
287+
}
288+
fields := make(map[string]clarity.ClarityValue)
289+
for _, field := range descriptor.Tuple.Fields {
290+
fieldValue, exists := tupleMap[field.Name]
291+
if !exists {
292+
return nil, fmt.Errorf("missing field '%s' in tuple", field.Name)
293+
}
294+
clarityFieldValue, err := ClarityTypeToClarityValue(field.Type, fieldValue)
295+
if err != nil {
296+
return nil, fmt.Errorf("failed to create tuple field '%s': %v", field.Name, err)
297+
}
298+
fields[field.Name] = clarityFieldValue
299+
}
300+
return clarity.NewTuple(fields), nil
301+
case descriptor.Response != nil:
302+
respMap, ok := value.(map[string]interface{})
303+
if !ok {
304+
return nil, fmt.Errorf("expected map for response value")
305+
}
306+
if okVal, exists := respMap["ok"]; exists {
307+
clarityOk, err := ClarityTypeToClarityValue(*descriptor.Response.Ok, okVal)
308+
if err != nil {
309+
return nil, fmt.Errorf("failed to create response ok: %v", err)
310+
}
311+
return clarity.NewResponseOk(clarityOk), nil
312+
}
313+
if errVal, exists := respMap["error"]; exists {
314+
clarityErr, err := ClarityTypeToClarityValue(*descriptor.Response.Error, errVal)
315+
if err != nil {
316+
return nil, fmt.Errorf("failed to create response error: %v", err)
317+
}
318+
return clarity.NewResponseErr(clarityErr), nil
319+
}
320+
return nil, fmt.Errorf("response must have either 'ok' or 'error'")
321+
case descriptor.Principal != nil:
322+
valStr, ok := value.(string)
323+
if !ok {
324+
return nil, fmt.Errorf("expected string for principal value")
325+
}
326+
return clarity.StringToPrincipal(valStr)
327+
case descriptor.TraitReference != nil:
328+
valStr, ok := value.(string)
329+
if !ok {
330+
return nil, fmt.Errorf("expected string for trait_reference value")
331+
}
332+
return clarity.StringToPrincipal(valStr)
333+
case descriptor.None != nil:
334+
return clarity.NewOptionNone(), nil
335+
default:
336+
return nil, fmt.Errorf("unsupported Clarity type descriptor")
337+
}
338+
}

0 commit comments

Comments
 (0)