decode.gno
5.56 Kb · 294 lines
1package abi
2
3import (
4 "encoding/binary"
5
6 u256 "gno.land/p/onbloc/math/uint256"
7)
8
9// Decode ABI-decodes data according to schema using Solidity's params tuple form.
10func Decode(schema Schema, data []byte) ([]any, error) {
11 if len(data)%wordSize != 0 {
12 return nil, ErrInvalidData
13 }
14
15 d := decoder{data: data, maxDepth: maxDecodeDepth}
16
17 return d.tupleAt(schema.Fields, 0, len(data), 0)
18}
19
20type decoder struct {
21 data []byte
22 maxDepth int
23}
24
25func (d *decoder) tupleAt(fields []Field, start, end, depth int) ([]any, error) {
26 depth++
27 if depth > d.maxDepth {
28 return nil, ErrDepthLimit
29 }
30
31 if start < 0 || end < start || end > len(d.data) {
32 return nil, ErrInvalidData
33 }
34
35 out := make([]any, len(fields))
36 cursor := start
37
38 for i, field := range fields {
39 dyn := isDynamic(field)
40 size := wordSize
41
42 if !dyn {
43 s, err := staticHeadSize(field)
44 if err != nil {
45 return nil, err
46 }
47
48 size = s
49 }
50
51 // prevent `cursor+size` overflow
52 if size < 0 || cursor > end || size > end-cursor {
53 return nil, ErrInvalidData
54 }
55
56 if dyn {
57 offset, err := decodeWordUint64(d.data[cursor : cursor+wordSize])
58 if err != nil {
59 return nil, err
60 }
61
62 if offset > uint64(end-start) || offset%wordSize != 0 {
63 return nil, ErrInvalidData
64 }
65
66 pos := start + int(offset)
67
68 out[i], err = d.valueAt(field, pos, end, depth)
69 if err != nil {
70 return nil, err
71 }
72
73 cursor += wordSize
74 } else {
75 v, err := d.staticValue(field, cursor, end, depth)
76 if err != nil {
77 return nil, err
78 }
79
80 out[i] = v
81 cursor += size
82 }
83 }
84
85 return out, nil
86}
87
88func (d *decoder) valueAt(field Field, pos, end, depth int) (any, error) {
89 switch field.Type {
90 case TypeBytes:
91 return d.bytesAt(pos, end)
92 case TypeString:
93 bz, err := d.bytesAt(pos, end)
94 if err != nil {
95 return nil, err
96 }
97
98 return string(bz), nil
99 case TypeStruct:
100 if field.Sub == nil {
101 return nil, ErrInvalidSchema
102 }
103
104 return d.tupleAt(field.Sub.Fields, pos, end, depth)
105 case TypeArray:
106 if field.Elem == nil {
107 return nil, ErrInvalidSchema
108 }
109
110 return d.arrayAt(*field.Elem, pos, end, depth)
111 default:
112 return d.staticValue(field, pos, end, depth)
113 }
114}
115
116func (d *decoder) staticValue(field Field, pos, end, depth int) (any, error) {
117 if pos+wordSize > end {
118 return nil, ErrInvalidData
119 }
120
121 switch field.Type {
122 case TypeUint8:
123 v, err := decodeWordUint64(d.data[pos : pos+wordSize])
124 if err != nil || v > 255 {
125 return nil, ErrInvalidData
126 }
127
128 return uint8(v), nil
129 case TypeUint32:
130 v, err := decodeWordUint64(d.data[pos : pos+wordSize])
131 if err != nil || v > 0xFFFFFFFF {
132 return nil, ErrInvalidData
133 }
134
135 return uint32(v), nil
136 case TypeUint64:
137 v, err := decodeWordUint64(d.data[pos : pos+wordSize])
138 if err != nil {
139 return nil, err
140 }
141
142 return v, nil
143 case TypeUint256:
144 var out u256.Uint
145 out.SetBytes32(d.data[pos : pos+wordSize])
146
147 return &out, nil
148 case TypeBytes32:
149 var out [32]byte
150 copy(out[:], d.data[pos:pos+wordSize])
151
152 return out, nil
153 case TypeBool:
154 v, err := decodeWordUint64(d.data[pos : pos+wordSize])
155 if err != nil || v > 1 {
156 return nil, ErrInvalidData
157 }
158
159 return v == 1, nil
160 case TypeAddress:
161 for i := pos; i < pos+12; i++ {
162 if d.data[i] != 0 {
163 return nil, ErrInvalidData
164 }
165 }
166
167 out := make([]byte, 20)
168 copy(out, d.data[pos+12:pos+wordSize])
169
170 return out, nil
171 case TypeStruct:
172 if field.Sub == nil {
173 return nil, ErrInvalidSchema
174 }
175
176 return d.tupleAt(field.Sub.Fields, pos, end, depth)
177 default:
178 return nil, ErrUnsupportedType
179 }
180}
181
182func (d *decoder) arrayAt(elem Field, pos, end, depth int) ([]any, error) {
183 depth++
184 if depth > d.maxDepth {
185 return nil, ErrDepthLimit
186 }
187
188 if pos > end || wordSize > end-pos {
189 return nil, ErrInvalidData
190 }
191
192 length, err := decodeWordUint64(d.data[pos : pos+wordSize])
193 if err != nil {
194 return nil, err
195 }
196
197 if length > uint64((end-pos)/wordSize) {
198 return nil, ErrInvalidData
199 }
200
201 n := int(length)
202 out := make([]any, n)
203
204 headStart := pos + wordSize
205 if isDynamic(elem) {
206 if n > (end-headStart)/wordSize {
207 return nil, ErrInvalidData
208 }
209
210 for i := 0; i < n; i++ {
211 offset, err := decodeWordUint64(d.data[headStart+i*wordSize : headStart+(i+1)*wordSize])
212 if err != nil {
213 return nil, err
214 }
215
216 if offset > uint64(end-headStart) || offset%wordSize != 0 {
217 return nil, ErrInvalidData
218 }
219
220 elemPos := headStart + int(offset)
221
222 out[i], err = d.valueAt(elem, elemPos, end, depth)
223 if err != nil {
224 return nil, err
225 }
226 }
227
228 return out, nil
229 }
230
231 elemSize, err := staticHeadSize(elem)
232 if err != nil {
233 return nil, err
234 }
235
236 if elemSize <= 0 || n > (end-headStart)/elemSize {
237 return nil, ErrInvalidData
238 }
239
240 for i := 0; i < n; i++ {
241 out[i], err = d.staticValue(elem, headStart+i*elemSize, end, depth)
242 if err != nil {
243 return nil, err
244 }
245 }
246
247 return out, nil
248}
249
250func (d *decoder) bytesAt(pos, end int) ([]byte, error) {
251 if pos+wordSize > end {
252 return nil, ErrInvalidData
253 }
254
255 length, err := decodeWordUint64(d.data[pos : pos+wordSize])
256 if err != nil {
257 return nil, err
258 }
259
260 start := pos + wordSize
261 remaining := uint64(end - start)
262
263 padded := roundUp32(length)
264 if padded < length || length > remaining || padded > remaining {
265 return nil, ErrInvalidData
266 }
267
268 out := make([]byte, int(length))
269 copy(out, d.data[start:start+int(length)])
270
271 return out, nil
272}
273
274func decodeWordUint64(word []byte) (uint64, error) {
275 if len(word) != wordSize {
276 return 0, ErrInvalidData
277 }
278
279 for i := 0; i < 24; i++ {
280 if word[i] != 0 {
281 return 0, ErrInvalidData
282 }
283 }
284
285 return binary.BigEndian.Uint64(word[24:32]), nil
286}
287
288func roundUp32(v uint64) uint64 {
289 if v%wordSize == 0 {
290 return v
291 }
292
293 return v + wordSize - v%wordSize
294}