Skip to content

Commit fe00c9a

Browse files
committed
codec: create codec module
1 parent f932111 commit fe00c9a

7 files changed

Lines changed: 1002 additions & 0 deletions

File tree

codec/decode_util.go

Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
package codec
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"fmt"
7+
"math"
8+
"math/big"
9+
"reflect"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/aptos-labs/aptos-go-sdk"
14+
"github.com/ethereum/go-ethereum/common"
15+
"github.com/go-viper/mapstructure/v2"
16+
)
17+
18+
func DecodeAptosJsonArray(from []any, to ...any) error {
19+
if len(to) != len(from) {
20+
return fmt.Errorf("mismatched from/to arguments")
21+
}
22+
23+
for i := range from {
24+
if err := DecodeAptosJsonValue(from[i], to[i]); err != nil {
25+
return fmt.Errorf("failed to decode value: %w", err)
26+
}
27+
}
28+
return nil
29+
}
30+
31+
func DecodeAptosJsonValue(from any, to any) error {
32+
// If `to` is a pointer to `any`, directly assign the value
33+
toValue := reflect.ValueOf(to)
34+
if toValue.Kind() == reflect.Ptr && !toValue.IsNil() {
35+
toElem := toValue.Elem()
36+
if toElem.Kind() == reflect.Interface && toElem.Type() == reflect.TypeOf((*any)(nil)).Elem() {
37+
toElem.Set(reflect.ValueOf(from))
38+
return nil
39+
}
40+
}
41+
42+
config := &mapstructure.DecoderConfig{
43+
DecodeHook: mapstructure.ComposeDecodeHookFunc(
44+
hexStringHook,
45+
numericStringHook,
46+
booleanHook,
47+
arrayHook,
48+
mapstructure.StringToTimeDurationHookFunc(),
49+
),
50+
Result: to,
51+
WeaklyTypedInput: true,
52+
MatchName: func(mapKey, fieldName string) bool {
53+
// Aptos uses snake_case for field names, while Go uses CamelCase,
54+
// remove underscores from field names to match Aptos field names
55+
fieldName = strings.ReplaceAll(fieldName, "_", "")
56+
mapKey = strings.ReplaceAll(mapKey, "_", "")
57+
return strings.EqualFold(mapKey, fieldName)
58+
},
59+
}
60+
61+
decoder, err := mapstructure.NewDecoder(config)
62+
if err != nil {
63+
return fmt.Errorf("failed to create decoder: %+w", err)
64+
}
65+
66+
return decoder.Decode(from)
67+
}
68+
69+
func hexStringHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
70+
if f.Kind() != reflect.String {
71+
return data, nil
72+
}
73+
74+
str, ok := data.(string)
75+
if !ok || !strings.HasPrefix(str, "0x") {
76+
return data, nil
77+
}
78+
79+
str = strings.TrimPrefix(str, "0x")
80+
81+
// Handle single-field struct case first by recursing via DecodeAptosJsonValue
82+
if t.Kind() == reflect.Struct && t.NumField() == 1 {
83+
field := t.Field(0)
84+
// Create a new zero value struct
85+
newStructVal := reflect.New(t).Elem()
86+
// Get a pointer to the field within the new struct
87+
fieldPtr := newStructVal.Field(0).Addr().Interface()
88+
89+
// Recursively decode the original hex string data into the field pointer
90+
// DecodeAptosJsonValue will apply the appropriate hooks (including this one)
91+
// for the field's type.
92+
if err := DecodeAptosJsonValue(data, fieldPtr); err != nil {
93+
return nil, fmt.Errorf("failed decoding hex string for single-field struct %v field %s (%v): %w", t, field.Name, field.Type, err)
94+
}
95+
// Return the populated struct instance
96+
return newStructVal.Interface(), nil
97+
}
98+
99+
switch t.Kind() {
100+
case reflect.String:
101+
return data, nil
102+
case reflect.Slice:
103+
if t.Elem().Kind() != reflect.Uint8 {
104+
return nil, fmt.Errorf("unsupported target slice element type for hex string conversion: %v", t.Elem().Kind())
105+
}
106+
if str == "" {
107+
// hex.DecodeString returns an error if the string is empty
108+
return []uint8{}, nil
109+
} else if len(str)%2 == 1 {
110+
// hex.DecodeString does not support odd length strings
111+
str = "0" + str
112+
}
113+
bytes, err := hex.DecodeString(str)
114+
if err != nil {
115+
return nil, fmt.Errorf("failed to decode hex string %q: %w", str, err)
116+
}
117+
return bytes, nil
118+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
119+
val, err := strconv.ParseUint(str, 16, 64)
120+
if err != nil {
121+
return nil, fmt.Errorf("failed to parse hex to uint: %w", err)
122+
}
123+
if overflowUint(t, val) {
124+
return nil, fmt.Errorf("value %d overflows %v", val, t)
125+
}
126+
return reflect.ValueOf(val).Convert(t).Interface(), nil
127+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
128+
val, err := strconv.ParseInt(str, 16, 64)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to parse hex to int: %w", err)
131+
}
132+
if overflowInt(t, val) {
133+
return nil, fmt.Errorf("value %d overflows %v", val, t)
134+
}
135+
return reflect.ValueOf(val).Convert(t).Interface(), nil
136+
case reflect.Ptr:
137+
if t == reflect.TypeOf((*big.Int)(nil)) {
138+
bi := new(big.Int)
139+
_, ok := bi.SetString(str, 16)
140+
if !ok {
141+
return nil, fmt.Errorf("failed to parse hex string as big.Int: %s", str)
142+
}
143+
return bi, nil
144+
}
145+
if t == reflect.TypeOf((*common.Address)(nil)) {
146+
addr := common.HexToAddress(str)
147+
return &addr, nil
148+
}
149+
if t == reflect.TypeOf((*common.Hash)(nil)) {
150+
hash := common.HexToHash(str)
151+
return &hash, nil
152+
}
153+
case reflect.Array:
154+
if t == reflect.TypeOf(common.Address{}) {
155+
addr := common.HexToAddress(str)
156+
return addr, nil
157+
}
158+
if t == reflect.TypeOf(common.Hash{}) {
159+
addr := common.HexToHash(str)
160+
return addr, nil
161+
}
162+
if t == reflect.TypeOf(aptos.AccountAddress{}) {
163+
addr := aptos.AccountAddress{}
164+
err := addr.ParseStringRelaxed(str)
165+
if err != nil {
166+
return nil, fmt.Errorf("failed to parse Aptos AccountAddress from string: %w", err)
167+
}
168+
return addr, nil
169+
}
170+
if t.Elem().Kind() == reflect.Uint8 {
171+
if str == "" {
172+
// hex.DecodeString returns an error if the string is empty
173+
return []uint8{}, nil
174+
} else if len(str)%2 == 1 {
175+
// hex.DecodeString does not support odd length strings
176+
str = "0" + str
177+
}
178+
bytes, err := hex.DecodeString(str)
179+
if err != nil {
180+
return nil, fmt.Errorf("failed to decode hex string %q: %w", str, err)
181+
}
182+
if len(bytes) != t.Len() {
183+
return nil, fmt.Errorf("hex string %q has incorrect length for u8 array, got %d, expected %d", str, len(bytes), t.Len())
184+
}
185+
// Create array of the correct type and copy bytes into it
186+
arrayVal := reflect.New(t).Elem()
187+
reflect.Copy(arrayVal, reflect.ValueOf(bytes))
188+
return arrayVal.Interface(), nil
189+
}
190+
return nil, fmt.Errorf("unsupported target array element type for hex string conversion: %v", t.Elem().Kind())
191+
default:
192+
}
193+
194+
return nil, fmt.Errorf("unsupported target type for hex string conversion: %v", t.Kind())
195+
}
196+
197+
func numericStringHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
198+
var str string
199+
switch v := data.(type) {
200+
case string:
201+
str = v
202+
case json.Number:
203+
str = v.String()
204+
default:
205+
return data, nil
206+
}
207+
208+
if t.Kind() == reflect.Struct && t.NumField() == 1 {
209+
field := t.Field(0)
210+
newStructVal := reflect.New(t).Elem()
211+
fieldPtr := newStructVal.Field(0).Addr().Interface()
212+
213+
// Decode the original numeric string data into the field pointer
214+
if err := DecodeAptosJsonValue(str, fieldPtr); err != nil {
215+
return nil, fmt.Errorf("failed decoding numeric string for single-field struct %v field %s (%v): %w", t, field.Name, field.Type, err)
216+
}
217+
return newStructVal.Interface(), nil
218+
}
219+
220+
switch t.Kind() {
221+
case reflect.String:
222+
return str, nil
223+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
224+
val, err := strconv.ParseInt(str, 10, 64)
225+
if err != nil {
226+
return nil, fmt.Errorf("failed to parse string to int: %+w", err)
227+
}
228+
if overflowInt(t, val) {
229+
return nil, fmt.Errorf("value %d overflows %v", val, t)
230+
}
231+
return reflect.ValueOf(val).Convert(t).Interface(), nil
232+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
233+
val, err := strconv.ParseUint(str, 10, 64)
234+
if err != nil {
235+
return nil, fmt.Errorf("failed to parse string to uint: %+w", err)
236+
}
237+
if overflowUint(t, val) {
238+
return nil, fmt.Errorf("value %d overflows %v", val, t)
239+
}
240+
return reflect.ValueOf(val).Convert(t).Interface(), nil
241+
case reflect.Float32, reflect.Float64:
242+
val, err := strconv.ParseFloat(str, 64)
243+
if err != nil {
244+
return nil, fmt.Errorf("failed to parse string to float: %+w", err)
245+
}
246+
if overflowFloat(t, val) {
247+
return nil, fmt.Errorf("value %f overflows %v", val, t)
248+
}
249+
return reflect.ValueOf(val).Convert(t).Interface(), nil
250+
case reflect.Ptr:
251+
if t == reflect.TypeOf((*big.Int)(nil)) {
252+
bi := new(big.Int)
253+
_, ok := bi.SetString(str, 10)
254+
if !ok {
255+
return nil, fmt.Errorf("failed to parse string as big.Int: %s", str)
256+
}
257+
return bi, nil
258+
}
259+
default:
260+
}
261+
262+
return nil, fmt.Errorf("unsupported target type for numeric string conversion: %v", t.Kind())
263+
}
264+
265+
func booleanHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
266+
if f.Kind() != reflect.Bool {
267+
return data, nil
268+
}
269+
270+
boolValue, ok := data.(bool)
271+
if !ok {
272+
// This should technically not happen if f.Kind() == reflect.Bool
273+
return data, nil
274+
}
275+
276+
// Handle single-field struct case first by recursing via DecodeAptosJsonValue
277+
if t.Kind() == reflect.Struct && t.NumField() == 1 {
278+
field := t.Field(0)
279+
newStructVal := reflect.New(t).Elem()
280+
fieldPtr := newStructVal.Field(0).Addr().Interface()
281+
282+
// Decode the original boolean data into the field pointer
283+
if err := DecodeAptosJsonValue(data, fieldPtr); err != nil {
284+
return nil, fmt.Errorf("failed decoding boolean for single-field struct %v field %s (%v): %w", t, field.Name, field.Type, err)
285+
}
286+
return newStructVal.Interface(), nil
287+
}
288+
289+
switch t.Kind() {
290+
case reflect.Bool:
291+
return boolValue, nil
292+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
293+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
294+
if boolValue {
295+
return reflect.ValueOf(1).Convert(t).Interface(), nil
296+
}
297+
return reflect.ValueOf(0).Convert(t).Interface(), nil
298+
case reflect.Ptr:
299+
if t == reflect.TypeOf((*big.Int)(nil)) {
300+
if boolValue {
301+
return big.NewInt(1), nil
302+
}
303+
return big.NewInt(0), nil
304+
}
305+
default:
306+
}
307+
308+
return nil, fmt.Errorf("unsupported target type for boolean conversion: %v", t.Kind())
309+
}
310+
311+
func arrayHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
312+
fKind := f.Kind()
313+
if fKind != reflect.Slice && fKind != reflect.Array {
314+
return data, nil
315+
}
316+
317+
if t.Kind() == reflect.Struct && t.NumField() == 1 {
318+
field := t.Field(0)
319+
newStructVal := reflect.New(t).Elem()
320+
fieldPtr := newStructVal.Field(0).Addr().Interface()
321+
322+
// Decode the original boolean data into the field pointer
323+
if err := DecodeAptosJsonValue(data, fieldPtr); err != nil {
324+
return nil, fmt.Errorf("failed decoding boolean for single-field struct %v field %s (%v): %w", t, field.Name, field.Type, err)
325+
}
326+
return newStructVal.Interface(), nil
327+
}
328+
329+
if t.Kind() != reflect.Slice {
330+
return data, nil
331+
}
332+
333+
sourceSlice := reflect.ValueOf(data)
334+
targetSlice := reflect.MakeSlice(t, sourceSlice.Len(), sourceSlice.Cap())
335+
336+
for i := range sourceSlice.Len() {
337+
sourceElem := sourceSlice.Index(i).Interface()
338+
targetElem := reflect.New(t.Elem()).Interface()
339+
340+
if err := DecodeAptosJsonValue(sourceElem, targetElem); err != nil {
341+
return nil, fmt.Errorf("failed to decode array element at index %d: %+w", i, err)
342+
}
343+
344+
targetSlice.Index(i).Set(reflect.ValueOf(targetElem).Elem())
345+
}
346+
347+
return targetSlice.Interface(), nil
348+
}
349+
350+
// TODO: modified from https://cs.opensource.google/go/go/+/master:src/reflect/type.go
351+
// where OverflowInt, OverflowUint, OverflowFloat was added to reflect.Type, use it once we
352+
// upgrade: https://go-review.googlesource.com/c/go/+/567296
353+
func overflowFloat(t reflect.Type, x float64) bool {
354+
k := t.Kind()
355+
switch k {
356+
case reflect.Float32:
357+
return overflowFloat32(x)
358+
case reflect.Float64:
359+
return false
360+
default:
361+
}
362+
panic("reflect: OverflowFloat of non-float type " + t.String())
363+
}
364+
365+
func overflowFloat32(x float64) bool {
366+
if x < 0 {
367+
x = -x
368+
}
369+
return math.MaxFloat32 < x && x <= math.MaxFloat64
370+
}
371+
372+
func overflowInt(t reflect.Type, x int64) bool {
373+
k := t.Kind()
374+
switch k {
375+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
376+
bitSize := t.Size() * 8
377+
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
378+
return x != trunc
379+
default:
380+
}
381+
panic("reflect: OverflowInt of non-int type " + t.String())
382+
}
383+
384+
func overflowUint(t reflect.Type, x uint64) bool {
385+
k := t.Kind()
386+
switch k {
387+
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
388+
bitSize := t.Size() * 8
389+
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
390+
return x != trunc
391+
default:
392+
}
393+
panic("reflect: OverflowUint of non-uint type " + t.String())
394+
}

0 commit comments

Comments
 (0)