summaryrefslogtreecommitdiff
path: root/data/abi/abi_type.go
blob: 35351702770ec1fbb65508cb2945d2c2fe167667 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
// Copyright (C) 2019-2021 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package abi

import (
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

/*
   ABI-Types: uint<N>: An N-bit unsigned integer (8 <= N <= 512 and N % 8 = 0).
            | byte (alias for uint8)
            | ufixed <N> x <M> (8 <= N <= 512, N % 8 = 0, and 0 < M <= 160)
            | bool
            | address (alias for byte[32])
            | <type> [<N>]
            | <type> []
            | string
            | (T1, ..., Tn)
*/

// BaseType is an type-alias for uint32. A BaseType value indicates the type of an ABI value.
type BaseType uint32

const (
	// Uint is the index (0) for `Uint` type in ABI encoding.
	Uint BaseType = iota
	// Byte is the index (1) for `Byte` type in ABI encoding.
	Byte
	// Ufixed is the index (2) for `UFixed` type in ABI encoding.
	Ufixed
	// Bool is the index (3) for `Bool` type in ABI encoding.
	Bool
	// ArrayStatic is the index (4) for static length array (<type>[length]) type in ABI encoding.
	ArrayStatic
	// Address is the index (5) for `Address` type in ABI encoding (an type alias of Byte[32]).
	Address
	// ArrayDynamic is the index (6) for dynamic length array (<type>[]) type in ABI encoding.
	ArrayDynamic
	// String is the index (7) for `String` type in ABI encoding (an type alias of Byte[]).
	String
	// Tuple is the index (8) for tuple `(<type 0>, ..., <type k>)` in ABI encoding.
	Tuple
)

// Type is the struct that stores information about an ABI value's type.
type Type struct {
	abiTypeID  BaseType
	childTypes []Type

	// only can be applied to `uint` bitSize <N> or `ufixed` bitSize <N>
	bitSize uint16
	// only can be applied to `ufixed` precision <M>
	precision uint16

	// length for static array / tuple
	/*
		by ABI spec, len over binary array returns number of bytes
		the type is uint16, which allows for only lenth in [0, 2^16 - 1]
		representation of static length can only be constrained in uint16 type
	*/
	// NOTE may want to change back to uint32/uint64
	staticLength uint16
}

// String serialize an ABI Type to a string in ABI encoding.
func (t Type) String() string {
	switch t.abiTypeID {
	case Uint:
		return fmt.Sprintf("uint%d", t.bitSize)
	case Byte:
		return "byte"
	case Ufixed:
		return fmt.Sprintf("ufixed%dx%d", t.bitSize, t.precision)
	case Bool:
		return "bool"
	case ArrayStatic:
		return fmt.Sprintf("%s[%d]", t.childTypes[0].String(), t.staticLength)
	case Address:
		return "address"
	case ArrayDynamic:
		return t.childTypes[0].String() + "[]"
	case String:
		return "string"
	case Tuple:
		typeStrings := make([]string, len(t.childTypes))
		for i := 0; i < len(t.childTypes); i++ {
			typeStrings[i] = t.childTypes[i].String()
		}
		return "(" + strings.Join(typeStrings, ",") + ")"
	default:
		panic("Type Serialization Error, fail to infer from abiTypeID (bruh you shouldn't be here)")
	}
}

var staticArrayRegexp = regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`)
var ufixedRegexp = regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`)

// TypeOf parses an ABI type string.
// For example: `TypeOf("(uint64,byte[])")`
func TypeOf(str string) (Type, error) {
	switch {
	case strings.HasSuffix(str, "[]"):
		arrayArgType, err := TypeOf(str[:len(str)-2])
		if err != nil {
			return Type{}, err
		}
		return makeDynamicArrayType(arrayArgType), nil
	case strings.HasSuffix(str, "]"):
		stringMatches := staticArrayRegexp.FindStringSubmatch(str)
		// match the string itself, array element type, then array length
		if len(stringMatches) != 3 {
			return Type{}, fmt.Errorf("static array ill formated: %s", str)
		}
		// guaranteed that the length of array is existing
		arrayLengthStr := stringMatches[2]
		// allowing only decimal static array length, with limit size to 2^16 - 1
		arrayLength, err := strconv.ParseUint(arrayLengthStr, 10, 16)
		if err != nil {
			return Type{}, err
		}
		// parse the array element type
		arrayType, err := TypeOf(stringMatches[1])
		if err != nil {
			return Type{}, err
		}
		return makeStaticArrayType(arrayType, uint16(arrayLength)), nil
	case strings.HasPrefix(str, "uint"):
		typeSize, err := strconv.ParseUint(str[4:], 10, 16)
		if err != nil {
			return Type{}, fmt.Errorf("ill formed uint type: %s", str)
		}
		return makeUintType(int(typeSize))
	case str == "byte":
		return byteType, nil
	case strings.HasPrefix(str, "ufixed"):
		stringMatches := ufixedRegexp.FindStringSubmatch(str)
		// match string itself, then type-bitSize, and type-precision
		if len(stringMatches) != 3 {
			return Type{}, fmt.Errorf("ill formed ufixed type: %s", str)
		}
		// guaranteed that there are 2 uint strings in ufixed string
		ufixedSize, err := strconv.ParseUint(stringMatches[1], 10, 16)
		if err != nil {
			return Type{}, err
		}
		ufixedPrecision, err := strconv.ParseUint(stringMatches[2], 10, 16)
		if err != nil {
			return Type{}, err
		}
		return makeUfixedType(int(ufixedSize), int(ufixedPrecision))
	case str == "bool":
		return boolType, nil
	case str == "address":
		return addressType, nil
	case str == "string":
		return stringType, nil
	case len(str) >= 2 && str[0] == '(' && str[len(str)-1] == ')':
		tupleContent, err := parseTupleContent(str[1 : len(str)-1])
		if err != nil {
			return Type{}, err
		}
		tupleTypes := make([]Type, len(tupleContent))
		for i := 0; i < len(tupleContent); i++ {
			ti, err := TypeOf(tupleContent[i])
			if err != nil {
				return Type{}, err
			}
			tupleTypes[i] = ti
		}
		return MakeTupleType(tupleTypes)
	default:
		return Type{}, fmt.Errorf("cannot convert a string %s to an ABI type", str)
	}
}

// segment keeps track of the start and end of a segment in a string.
type segment struct{ left, right int }

// parseTupleContent splits an ABI encoded string for tuple type into multiple sub-strings.
// Each sub-string represents a content type of the tuple type.
// The argument str is the content between parentheses of tuple, i.e.
// (...... str ......)
//  ^               ^
func parseTupleContent(str string) ([]string, error) {
	// if the tuple type content is empty (which is also allowed)
	// just return the empty string list
	if len(str) == 0 {
		return []string{}, nil
	}

	// the following 2 checks want to make sure input string can be separated by comma
	// with form: "...substr_0,...substr_1,...,...substr_k"

	// str should noe have leading/tailing comma
	if strings.HasSuffix(str, ",") || strings.HasPrefix(str, ",") {
		return []string{}, fmt.Errorf("parsing error: tuple content should not start with comma")
	}

	// str should not have consecutive commas contained
	if strings.Contains(str, ",,") {
		return []string{}, fmt.Errorf("no consecutive commas")
	}

	var parenSegmentRecord = make([]segment, 0)
	var stack []int

	// get the most exterior parentheses segment (not overlapped by other parentheses)
	// illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"]
	// once iterate to left paren (, stack up by 1 in stack
	// iterate to right paren ), pop 1 in stack
	// if iterate to right paren ) with stack height 0, find a parenthesis segment "(******)"
	for index, chr := range str {
		if chr == '(' {
			stack = append(stack, index)
		} else if chr == ')' {
			if len(stack) == 0 {
				return []string{}, fmt.Errorf("unpaired parentheses: %s", str)
			}
			leftParenIndex := stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			if len(stack) == 0 {
				parenSegmentRecord = append(parenSegmentRecord, segment{
					left:  leftParenIndex,
					right: index,
				})
			}
		}
	}
	if len(stack) != 0 {
		return []string{}, fmt.Errorf("unpaired parentheses: %s", str)
	}

	// take out tuple-formed type str in tuple argument
	strCopied := str
	for i := len(parenSegmentRecord) - 1; i >= 0; i-- {
		parenSeg := parenSegmentRecord[i]
		strCopied = strCopied[:parenSeg.left] + strCopied[parenSeg.right+1:]
	}

	// split the string without parenthesis segments
	tupleStrSegs := strings.Split(strCopied, ",")

	// the empty strings are placeholders for parenthesis segments
	// put the parenthesis segments back into segment list
	parenSegCount := 0
	for index, segStr := range tupleStrSegs {
		if segStr == "" {
			parenSeg := parenSegmentRecord[parenSegCount]
			tupleStrSegs[index] = str[parenSeg.left : parenSeg.right+1]
			parenSegCount++
		}
	}

	return tupleStrSegs, nil
}

// makeUintType makes `Uint` ABI type by taking a type bitSize argument.
// The range of type bitSize is [8, 512] and type bitSize % 8 == 0.
func makeUintType(typeSize int) (Type, error) {
	if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 {
		return Type{}, fmt.Errorf("unsupported uint type bitSize: %d", typeSize)
	}
	return Type{
		abiTypeID: Uint,
		bitSize:   uint16(typeSize),
	}, nil
}

var (
	// byteType is ABI type constant for byte
	byteType = Type{abiTypeID: Byte}

	// boolType is ABI type constant for bool
	boolType = Type{abiTypeID: Bool}

	// addressType is ABI type constant for address
	addressType = Type{abiTypeID: Address}

	// stringType is ABI type constant for string
	stringType = Type{abiTypeID: String}
)

// makeUfixedType makes `UFixed` ABI type by taking type bitSize and type precision as arguments.
// The range of type bitSize is [8, 512] and type bitSize % 8 == 0.
// The range of type precision is [1, 160].
func makeUfixedType(typeSize int, typePrecision int) (Type, error) {
	if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 {
		return Type{}, fmt.Errorf("unsupported ufixed type bitSize: %d", typeSize)
	}
	if typePrecision > 160 || typePrecision < 1 {
		return Type{}, fmt.Errorf("unsupported ufixed type precision: %d", typePrecision)
	}
	return Type{
		abiTypeID: Ufixed,
		bitSize:   uint16(typeSize),
		precision: uint16(typePrecision),
	}, nil
}

// makeStaticArrayType makes static length array ABI type by taking
// array element type and array length as arguments.
func makeStaticArrayType(argumentType Type, arrayLength uint16) Type {
	return Type{
		abiTypeID:    ArrayStatic,
		childTypes:   []Type{argumentType},
		staticLength: arrayLength,
	}
}

// makeDynamicArrayType makes dynamic length array by taking array element type as argument.
func makeDynamicArrayType(argumentType Type) Type {
	return Type{
		abiTypeID:  ArrayDynamic,
		childTypes: []Type{argumentType},
	}
}

// MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument.
func MakeTupleType(argumentTypes []Type) (Type, error) {
	if len(argumentTypes) >= math.MaxUint16 {
		return Type{}, fmt.Errorf("tuple type child type number larger than maximum uint16 error")
	}
	return Type{
		abiTypeID:    Tuple,
		childTypes:   argumentTypes,
		staticLength: uint16(len(argumentTypes)),
	}, nil
}

// Equal method decides the equality of two types: t == t0.
func (t Type) Equal(t0 Type) bool {
	if t.abiTypeID != t0.abiTypeID {
		return false
	}
	if t.precision != t0.precision || t.bitSize != t0.bitSize {
		return false
	}
	if t.staticLength != t0.staticLength {
		return false
	}
	if len(t.childTypes) != len(t0.childTypes) {
		return false
	}
	for i := 0; i < len(t.childTypes); i++ {
		if !t.childTypes[i].Equal(t0.childTypes[i]) {
			return false
		}
	}

	return true
}

// IsDynamic method decides if an ABI type is dynamic or static.
func (t Type) IsDynamic() bool {
	switch t.abiTypeID {
	case ArrayDynamic, String:
		return true
	default:
		for _, childT := range t.childTypes {
			if childT.IsDynamic() {
				return true
			}
		}
		return false
	}
}

// Assume that the current index on the list of type is an ABI bool type.
// It returns the difference between the current index and the index of the furthest consecutive Bool type.
func findBoolLR(typeList []Type, index int, delta int) int {
	until := 0
	for {
		curr := index + delta*until
		if typeList[curr].abiTypeID == Bool {
			if curr != len(typeList)-1 && delta > 0 {
				until++
			} else if curr > 0 && delta < 0 {
				until++
			} else {
				break
			}
		} else {
			until--
			break
		}
	}
	return until
}

const (
	addressByteSize      = 32
	singleByteSize       = 1
	singleBoolSize       = 1
	lengthEncodeByteSize = 2
)

// ByteLen method calculates the byte length of a static ABI type.
func (t Type) ByteLen() (int, error) {
	switch t.abiTypeID {
	case Address:
		return addressByteSize, nil
	case Byte:
		return singleByteSize, nil
	case Uint, Ufixed:
		return int(t.bitSize / 8), nil
	case Bool:
		return singleBoolSize, nil
	case ArrayStatic:
		if t.childTypes[0].abiTypeID == Bool {
			byteLen := int(t.staticLength+7) / 8
			return byteLen, nil
		}
		elemByteLen, err := t.childTypes[0].ByteLen()
		if err != nil {
			return -1, err
		}
		return int(t.staticLength) * elemByteLen, nil
	case Tuple:
		size := 0
		for i := 0; i < len(t.childTypes); i++ {
			if t.childTypes[i].abiTypeID == Bool {
				// search after bool
				after := findBoolLR(t.childTypes, i, 1)
				// shift the index
				i += after
				// get number of bool
				boolNum := after + 1
				size += (boolNum + 7) / 8
			} else {
				childByteSize, err := t.childTypes[i].ByteLen()
				if err != nil {
					return -1, err
				}
				size += childByteSize
			}
		}
		return size, nil
	default:
		return -1, fmt.Errorf("%s is a dynamic type", t.String())
	}
}

// IsTransactionType checks if a type string represents a transaction type
// argument, such as "txn", "pay", "keyreg", etc.
func IsTransactionType(s string) bool {
	switch s {
	case "txn", "pay", "keyreg", "acfg", "axfer", "afrz", "appl":
		return true
	default:
		return false
	}
}