Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}