Skip to content

Commit 8a1cf4d

Browse files
committed
Refactor JSON decoder to support nested SnakeRef array decoding
1 parent a017b7f commit 8a1cf4d

File tree

3 files changed

+168
-109
lines changed

3 files changed

+168
-109
lines changed

pkg/bindings/mcms/timelock/types.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ type ScheduleBatch struct {
6767
// Query ID of the change request.
6868
QueryID uint64 `tlb:"## 64"`
6969

70-
Calls common.SnakeData[Call] `tlb:"^"` // Array of calls to be scheduled // vec<Timelock_Call>
71-
Predecessor *big.Int `tlb:"## 256"` // Predecessor operation ID
72-
Salt *big.Int `tlb:"## 256"` // Salt used to derive the operation ID
73-
Delay uint64 `tlb:"## 64"` // Delay in seconds before the operation can be executed
70+
Calls common.SnakeRef[Call] `tlb:"^"` // Array of calls to be scheduled // vec<Timelock_Call>
71+
Predecessor *big.Int `tlb:"## 256"` // Predecessor operation ID
72+
Salt *big.Int `tlb:"## 256"` // Salt used to derive the operation ID
73+
Delay uint64 `tlb:"## 64"` // Delay in seconds before the operation can be executed
7474
}
7575

7676
// Cancel an operation.
@@ -99,9 +99,9 @@ type ExecuteBatch struct {
9999
// Query ID of the change request.
100100
QueryID uint64 `tlb:"## 64"`
101101

102-
Calls common.SnakeData[Call] `tlb:"^"` // Array of calls to be scheduled // vec<Timelock_Call>
103-
Predecessor *big.Int `tlb:"## 256"` // Predecessor operation ID
104-
Salt *big.Int `tlb:"## 256"` // Salt used to derive the operation ID
102+
Calls common.SnakeRef[Call] `tlb:"^"` // Array of calls to be scheduled // vec<Timelock_Call>
103+
Predecessor *big.Int `tlb:"## 256"` // Predecessor operation ID
104+
Salt *big.Int `tlb:"## 256"` // Salt used to derive the operation ID
105105
}
106106

107107
// Changes the minimum timelock duration for future operations.
@@ -182,7 +182,7 @@ type BypasserExecuteBatch struct {
182182
QueryID uint64 `tlb:"## 64"`
183183

184184
// Array of calls to be scheduled
185-
Calls common.SnakeData[Call] `tlb:"^"` // vec<Timelock_Call>
185+
Calls common.SnakeRef[Call] `tlb:"^"` // vec<Timelock_Call>
186186
}
187187

188188
// Updates the executor role check (enabled/disabled) which guards the execution of operations.
@@ -351,7 +351,7 @@ type Call struct {
351351
// Batch of transactions represented as a operation, which can be scheduled and executed.
352352
type OperationBatch struct {
353353
// Array of calls to be scheduled
354-
Calls common.SnakeData[Call] `tlb:"^"` // vec<Timelock_Call>
354+
Calls common.SnakeRef[Call] `tlb:"^"` // vec<Timelock_Call>
355355
// Predecessor operation ID
356356
Predecessor *big.Int `tlb:"## 256"`
357357
// Salt used to derive the operation ID

pkg/ton/debug/lib/lib.go

Lines changed: 120 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package lib
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"reflect"
78
"strconv"
@@ -71,7 +72,7 @@ func NewMessageInfo(name string, msg any) (MessageInfo, error) {
7172

7273
// NewMessageInfoFromCell attempts to decode the given cell using the provided TL-B candidates mapped by their opcodes.
7374
func NewMessageInfoFromCell(t cldf.ContractType, msg *cell.Cell, tlbs map[uint64]interface{}) (MessageInfo, error) {
74-
typeName, m, err := DecodeJSONMapFromCell(msg, tlbs)
75+
typeName, m, err := DecodeTLBValToJSON(msg, tlbs)
7576
if err != nil {
7677
return nil, fmt.Errorf("failed to decode message for contract %s: %w", t, err)
7778
}
@@ -81,87 +82,140 @@ func NewMessageInfoFromCell(t cldf.ContractType, msg *cell.Cell, tlbs map[uint64
8182
return NewMessageInfo(name, m)
8283
}
8384

84-
// DecodeJSONMapFromCell attempts to decode the given cell using the provided TL-B candidates mapped by their opcodes.
85-
func DecodeJSONMapFromCell(msg *cell.Cell, tlbs map[uint64]interface{}) (string, map[string]interface{}, error) {
86-
// 1.1 Try to decode *cell.Cell as one of the TLBs type by reading the opcode
87-
if msg == nil {
88-
return "", nil, &UnknownMessageError{}
89-
}
85+
func DecodeTLBStructToJSON(v interface{}, tlbs map[uint64]interface{}) (string, map[string]interface{}, error) {
86+
switch t := v.(type) {
87+
case nil:
88+
return "", nil, errors.New("can't decode nil as struct")
89+
case *cell.Cell:
90+
// Try to decode *cell.Cell as one of the TLBs type by reading the opcode
91+
r := t.BeginParse()
92+
if r.BitsLeft() == 0 {
93+
return "", nil, &UnknownMessageError{}
94+
}
95+
opCode, err := r.PreloadUInt(32)
96+
if err != nil {
97+
return "", nil, fmt.Errorf("failed to preload opcode: %w", err)
98+
}
9099

91-
r := msg.BeginParse()
92-
if r.BitsLeft() == 0 {
93-
return "", nil, &UnknownMessageError{}
94-
}
95-
opCode, err := r.PreloadUInt(32)
96-
if err != nil {
97-
return "", nil, fmt.Errorf("failed to preload opcode: %w", err)
98-
}
100+
i, ok := tlbs[opCode]
101+
if !ok {
102+
return "", nil, &UnknownMessageError{}
103+
}
99104

100-
i, ok := tlbs[opCode]
101-
if !ok {
102-
return "", nil, &UnknownMessageError{}
103-
}
105+
// Create new instance of the candidate type
106+
rt := reflect.TypeOf(i)
107+
inst := reflect.New(rt).Interface() // pointer to zero value
104108

105-
// create new instance of the candidate type
106-
rt := reflect.TypeOf(i)
107-
inst := reflect.New(rt).Interface() // pointer to zero value
109+
// Attempt decode - replace tlb.FromCell with the actual decode API you have
110+
if err = tlb.LoadFromCell(inst, r); err != nil {
111+
return "", nil, fmt.Errorf("failed to decode message for opcode 0x%X: %w", opCode, err)
112+
}
108113

109-
// attempt decode - replace tlb.FromCell with the actual decode API you have
110-
if err = tlb.LoadFromCell(inst, r); err != nil {
111-
return "", nil, fmt.Errorf("failed to decode message for opcode 0x%X: %w", opCode, err)
112-
}
114+
// Now decode loaded struct (internal *cell.Cell) fields recursively
115+
return DecodeTLBStructToJSON(inst, tlbs)
116+
default:
117+
// Iterate over the fields of the struct (reflect)
118+
rv := reflect.ValueOf(v)
119+
if rv.Kind() == reflect.Ptr {
120+
rv = rv.Elem()
121+
}
122+
if !rv.IsValid() {
123+
return "", nil, fmt.Errorf("failed to decode TLB struct - not valid value: type=%T; val=%v", t, rv)
124+
}
113125

114-
// Now decode internal *cell.Cell fields recursively
115-
// 2.1. Iterate over the fields of the struct (reflect)
116-
ckeys := make([]string, 0)
117-
for i := 0; i < rt.NumField(); i++ {
118-
f := rt.Field(i)
119-
120-
// 2.2. For each field, check if it's of type *cell.Cell
121-
if f.Type == reflect.TypeOf(&cell.Cell{}) {
122-
// 2.3. If so, check the json tag to determine the expected key
123-
k := f.Name
124-
jsonTag := f.Tag.Get("json")
126+
if rv.Kind() != reflect.Struct {
127+
return "", nil, fmt.Errorf("unable to decode as JSON map - not a structure: type=%T; val=%v", t, rv)
128+
}
129+
130+
out := make(map[string]interface{}, rv.NumField())
131+
rt := rv.Type()
132+
for i := 0; i < rv.NumField(); i++ {
133+
sf := rt.Field(i)
134+
// skip unexported fields (e.g. the magic field)
135+
if sf.PkgPath != "" {
136+
continue
137+
}
138+
139+
// check the json tag to determine the expected key
140+
k := sf.Name
141+
jsonTag := sf.Tag.Get("json")
125142
if jsonTag != "" {
126143
k = strings.Split(jsonTag, ",")[0] // parse json tag options (key)
127144
}
128145

129-
// 2.4. Source a set of keys that we need to decode recursively
130-
ckeys = append(ckeys, k)
146+
fv := rv.Field(i)
147+
_, decoded, err := DecodeTLBValToJSON(fv.Interface(), tlbs)
148+
if err != nil {
149+
return "", nil, fmt.Errorf("failed to decode TLB value: %w", err)
150+
}
151+
out[k] = decoded
131152
}
153+
return rt.Name(), out, nil
132154
}
155+
}
133156

134-
// 3.1. Decode the struct as JSON map[string]interface{} (default *cell.Cell marshalling)
135-
var rawMap map[string]interface{}
136-
rawBytes, err := json.Marshal(inst)
137-
if err != nil {
138-
return "", nil, fmt.Errorf("failed to marshal decoded message to JSON: %w", err)
139-
}
140-
err = json.Unmarshal(rawBytes, &rawMap)
141-
if err != nil {
142-
return "", nil, fmt.Errorf("failed to unmarshal decoded message JSON to map: %w", err)
143-
}
144-
145-
// 3.2. For each key in the sourced set, get the *cell.Cell value (decode from BOC)
146-
for _, ck := range ckeys {
147-
cBOC := rawMap[ck]
157+
func DecodeTLBValToJSON(v interface{}, tlbs map[uint64]interface{}) (string, interface{}, error) {
158+
switch t := v.(type) {
159+
case nil:
160+
return "<nil>", nil, nil
161+
case *cell.Cell:
162+
typeName, decoded, err := DecodeTLBStructToJSON(t, tlbs)
163+
if err != nil {
164+
return "Cell", t, nil // fallback if not a known struct
165+
}
148166

149-
cVal := &cell.Cell{}
150-
if err := json.Unmarshal([]byte(strconv.Quote(cBOC.(string))), cVal); err != nil {
151-
return "", nil, fmt.Errorf("failed to unmarshal BOC to cell: %s: %s: %w", ck, cBOC, err)
167+
return typeName, decoded, nil
168+
default:
169+
// for slices/arrays/structs/maps repeat normalization recursively
170+
rv := reflect.ValueOf(t)
171+
if !rv.IsValid() {
172+
return "<invalid>", nil, nil
152173
}
153174

154-
// 3.3. Try to decode recursively
155-
_, cMap, err := DecodeJSONMapFromCell(cVal, tlbs)
156-
if err != nil {
157-
// fallback to original BOC representation if fails
158-
continue
175+
switch rv.Kind() {
176+
case reflect.Slice, reflect.Array:
177+
out := make([]interface{}, rv.Len())
178+
for i := 0; i < rv.Len(); i++ {
179+
_, decoded, err := DecodeTLBValToJSON(rv.Index(i).Interface(), tlbs)
180+
if err != nil {
181+
return "", nil, err
182+
}
183+
out[i] = decoded
184+
}
185+
return rv.Type().String(), out, nil
186+
case reflect.Map:
187+
m := map[string]interface{}{}
188+
for _, k := range rv.MapKeys() {
189+
keyStr := fmt.Sprint(k.Interface())
190+
_, decoded, err := DecodeTLBValToJSON(rv.MapIndex(k).Interface(), tlbs)
191+
if err != nil {
192+
return "", nil, err
193+
}
194+
m[keyStr] = decoded
195+
}
196+
return rv.Type().String(), m, nil
197+
case reflect.Struct:
198+
// recurse on nested struct
199+
// create pointer to struct so DecodeTLBStructToJSON can handle exported fields
200+
ptr := reflect.New(rv.Type()).Interface()
201+
reflect.ValueOf(ptr).Elem().Set(rv)
202+
203+
// if there is a json.Marshaler (either on the value or the pointer), prefer it.
204+
jmType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
205+
if rv.CanAddr() && rv.Addr().Type().Implements(jmType) || rv.Type().Implements(jmType) {
206+
return "", v, nil
207+
}
208+
209+
typeName, decoded, err := DecodeTLBStructToJSON(ptr, tlbs)
210+
if err != nil {
211+
return "", nil, fmt.Errorf("failed to decode TLB struct: %w; val=%v", err, t)
212+
}
213+
214+
return typeName, decoded, nil
215+
default:
216+
return rv.Type().Name(), t, nil
159217
}
160-
rawMap[ck] = cMap
161218
}
162-
163-
return rt.Name(), rawMap, nil
164-
// 4.4 Finally, marshal the final map[string]interface{} as JSON string
165219
}
166220

167221
func MustNewTLBMap(types []interface{}) map[uint64]interface{} {
@@ -177,9 +231,8 @@ func MustNewTLBMap(types []interface{}) map[uint64]interface{} {
177231
func NewTLBMap(types []interface{}) (map[uint64]interface{}, error) {
178232
tlbs := make(map[uint64]interface{})
179233
for _, typ := range types {
180-
// Use reflection to get the magic number from the type
234+
// reflect to get the magic number from the struct
181235
rt := reflect.TypeOf(typ)
182-
183236
if rt.Field(0).Type != reflect.TypeOf(tlb.Magic{}) {
184237
return nil, fmt.Errorf("first field of %s is not of type Magic", rt.Name())
185238
}

0 commit comments

Comments
 (0)