diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go
new file mode 100644
index 0000000000..db4750bd00
--- /dev/null
+++ b/data/abi/abi_encode.go
@@ -0,0 +1,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 .
+
+package abi
+
+import (
+ "encoding/binary"
+ "fmt"
+ "math/big"
+)
+
+// arrayToTuple casts an array-like ABI Value into an ABI Value of Tuple type.
+// This is used in both ABI Encoding and Decoding.
+func (v Value) arrayToTuple() (Value, error) {
+ var childT []Type
+ var valueArr []Value
+
+ switch v.ABIType.abiTypeID {
+ case String:
+ strValue, err := v.GetString()
+ if err != nil {
+ return Value{}, err
+ }
+ strByte := []byte(strValue)
+
+ childT = make([]Type, len(strByte))
+ valueArr = make([]Value, len(strByte))
+
+ for i := 0; i < len(strByte); i++ {
+ childT[i] = MakeByteType()
+ valueArr[i] = MakeByte(strByte[i])
+ }
+ case Address:
+ addr, err := v.GetAddress()
+ if err != nil {
+ return Value{}, err
+ }
+
+ childT = make([]Type, addressByteSize)
+ valueArr = make([]Value, addressByteSize)
+
+ for i := 0; i < addressByteSize; i++ {
+ childT[i] = MakeByteType()
+ valueArr[i] = MakeByte(addr[i])
+ }
+ case ArrayStatic:
+ childT = make([]Type, v.ABIType.staticLength)
+ for i := 0; i < int(v.ABIType.staticLength); i++ {
+ childT[i] = v.ABIType.childTypes[0]
+ }
+ valueArr = v.value.([]Value)
+ case ArrayDynamic:
+ arrayElems := v.value.([]Value)
+ childT = make([]Type, len(arrayElems))
+ for i := 0; i < len(arrayElems); i++ {
+ childT[i] = v.ABIType.childTypes[0]
+ }
+ valueArr = arrayElems
+ default:
+ return Value{}, fmt.Errorf("value type not supported to conversion to tuple")
+ }
+
+ castedTupleType, err := MakeTupleType(childT)
+ if err != nil {
+ return Value{}, err
+ }
+
+ return Value{
+ ABIType: castedTupleType,
+ value: valueArr,
+ }, nil
+}
+
+// Encode method serialize the ABI value into a byte string of ABI encoding rule.
+func (v Value) Encode() ([]byte, error) {
+ switch v.ABIType.abiTypeID {
+ case Uint:
+ bigIntValue, err := v.GetUint()
+ if err != nil {
+ return []byte{}, err
+ }
+ // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use `fillbytes`
+ bigIntBytes := bigIntValue.Bytes()
+ buffer := make([]byte, v.ABIType.bitSize/8-uint16(len(bigIntBytes)))
+ buffer = append(buffer, bigIntBytes...)
+ return buffer, nil
+ case Ufixed:
+ ufixedValue, err := v.GetUfixed()
+ if err != nil {
+ return []byte{}, err
+ }
+ // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use `fillbytes`
+ encodeBuffer := ufixedValue.Bytes()
+ buffer := make([]byte, v.ABIType.bitSize/8-uint16(len(encodeBuffer)))
+ buffer = append(buffer, encodeBuffer...)
+ return buffer, nil
+ case Bool:
+ boolValue, err := v.GetBool()
+ if err != nil {
+ return []byte{}, err
+ }
+ if boolValue {
+ return []byte{0x80}, nil
+ }
+ return []byte{0x00}, nil
+ case Byte:
+ bytesValue, err := v.GetByte()
+ if err != nil {
+ return []byte{}, nil
+ }
+ return []byte{bytesValue}, nil
+ case ArrayStatic, Address:
+ convertedTuple, err := v.arrayToTuple()
+ if err != nil {
+ return []byte{}, err
+ }
+ return tupleEncoding(convertedTuple)
+ case ArrayDynamic, String:
+ convertedTuple, err := v.arrayToTuple()
+ if err != nil {
+ return []byte{}, err
+ }
+ length := len(convertedTuple.ABIType.childTypes)
+ lengthEncode := make([]byte, lengthEncodeByteSize)
+ binary.BigEndian.PutUint16(lengthEncode, uint16(length))
+
+ encoded, err := tupleEncoding(convertedTuple)
+ if err != nil {
+ return []byte{}, err
+ }
+ return append(lengthEncode, encoded...), nil
+ case Tuple:
+ return tupleEncoding(v)
+ default:
+ return []byte{}, fmt.Errorf("Encoding: unknown type error (bruh why you are here)")
+ }
+}
+
+// compressMultipleBool compress consecutive bool values into a byte in ABI tuple/array value.
+func compressMultipleBool(valueList []Value) (uint8, error) {
+ var res uint8 = 0
+ if len(valueList) > 8 {
+ return 0, fmt.Errorf("value list passed in should be no greater than length 8")
+ }
+ for i := 0; i < len(valueList); i++ {
+ if valueList[i].ABIType.abiTypeID != Bool {
+ return 0, fmt.Errorf("bool type not matching in compressMultipleBool")
+ }
+ boolVal, err := valueList[i].GetBool()
+ if err != nil {
+ return 0, err
+ }
+ if boolVal {
+ res |= 1 << uint(7-i)
+ }
+ }
+ return res, nil
+}
+
+// tupleEncoding encodes an ABI value of tuple type into an ABI encoded byte string.
+func tupleEncoding(v Value) ([]byte, error) {
+ if v.ABIType.abiTypeID != Tuple {
+ return []byte{}, fmt.Errorf("type not supported in tupleEncoding")
+ }
+ if len(v.ABIType.childTypes) >= (1 << 16) {
+ return []byte{}, fmt.Errorf("value abi type exceed 2^16")
+ }
+ tupleElems := v.value.([]Value)
+ if len(tupleElems) != len(v.ABIType.childTypes) {
+ return []byte{}, fmt.Errorf("tuple abi child type number unmatch with tuple argument number")
+ }
+
+ // for each tuple element value, it has a head/tail component
+ // we create slots for head/tail bytes now, store them and concat them later
+ heads := make([][]byte, len(v.ABIType.childTypes))
+ tails := make([][]byte, len(v.ABIType.childTypes))
+ isDynamicIndex := make(map[int]bool)
+
+ for i := 0; i < len(v.ABIType.childTypes); i++ {
+ if tupleElems[i].ABIType.IsDynamic() {
+ // if it is a dynamic value, the head component is not pre-determined
+ // we store an empty placeholder first, since we will need it in byte length calculation
+ headsPlaceholder := []byte{0x00, 0x00}
+ heads[i] = headsPlaceholder
+ // we keep track that the index points to a dynamic value
+ isDynamicIndex[i] = true
+ tailEncoding, err := tupleElems[i].Encode()
+ if err != nil {
+ return []byte{}, err
+ }
+ tails[i] = tailEncoding
+ } else {
+ if tupleElems[i].ABIType.abiTypeID == Bool {
+ // search previous bool
+ before := findBoolLR(v.ABIType.childTypes, i, -1)
+ // search after bool
+ after := findBoolLR(v.ABIType.childTypes, i, 1)
+ // append to heads and tails
+ if before%8 != 0 {
+ return []byte{}, fmt.Errorf("expected before has number of bool mod 8 = 0")
+ }
+ if after > 7 {
+ after = 7
+ }
+ compressed, err := compressMultipleBool(tupleElems[i : i+after+1])
+ if err != nil {
+ return []byte{}, err
+ }
+ heads[i] = []byte{compressed}
+ i += after
+ } else {
+ encodeTi, err := tupleElems[i].Encode()
+ if err != nil {
+ return []byte{}, err
+ }
+ heads[i] = encodeTi
+ }
+ isDynamicIndex[i] = false
+ }
+ }
+
+ // adjust heads for dynamic type
+ // since head size can be pre-determined (for we are storing static value and dynamic value index in head)
+ // we accumulate the head size first
+ // (also note that though head size is pre-determined, head value is not necessarily pre-determined)
+ headLength := 0
+ for _, headTi := range heads {
+ headLength += len(headTi)
+ }
+
+ // when we iterate through the heads (byte slice), we need to find heads for dynamic values
+ // the head should correspond to the start index: len( head(x[1]) ... head(x[N]) tail(x[1]) ... tail(x[i-1]) ).
+ tailCurrLength := 0
+ for i := 0; i < len(heads); i++ {
+ if isDynamicIndex[i] {
+ // calculate where the index of dynamic value encoding byte start
+ headValue := headLength + tailCurrLength
+ if headValue >= (1 << 16) {
+ return []byte{}, fmt.Errorf("encoding error: byte length exceed 2^16")
+ }
+ binary.BigEndian.PutUint16(heads[i], uint16(headValue))
+ }
+ // accumulate the current tailing dynamic encoding bytes length.
+ tailCurrLength += len(tails[i])
+ }
+
+ // concat everything as the abi encoded bytes
+ encoded := make([]byte, 0, headLength+tailCurrLength)
+ for _, head := range heads {
+ encoded = append(encoded, head...)
+ }
+ for _, tail := range tails {
+ encoded = append(encoded, tail...)
+ }
+ return encoded, nil
+}
+
+// Decode takes an ABI encoded byte string and a target ABI type,
+// and decodes the bytes into an ABI Value.
+func Decode(valueByte []byte, valueType Type) (Value, error) {
+ switch valueType.abiTypeID {
+ case Uint:
+ if len(valueByte) != int(valueType.bitSize)/8 {
+ return Value{},
+ fmt.Errorf("uint%d decode: expected byte length %d, but got byte length %d",
+ valueType.bitSize, valueType.bitSize/8, len(valueByte))
+ }
+ uintValue := new(big.Int).SetBytes(valueByte)
+ return MakeUint(uintValue, valueType.bitSize)
+ case Ufixed:
+ if len(valueByte) != int(valueType.bitSize)/8 {
+ return Value{},
+ fmt.Errorf("ufixed%dx%d decode: expected length %d, got byte length %d",
+ valueType.bitSize, valueType.precision, valueType.bitSize/8, len(valueByte))
+ }
+ ufixedNumerator := new(big.Int).SetBytes(valueByte)
+ return MakeUfixed(ufixedNumerator, valueType.bitSize, valueType.precision)
+ case Bool:
+ if len(valueByte) != 1 {
+ return Value{}, fmt.Errorf("boolean byte should be length 1 byte")
+ }
+ var boolValue bool
+ if valueByte[0] == 0x00 {
+ boolValue = false
+ } else if valueByte[0] == 0x80 {
+ boolValue = true
+ } else {
+ return Value{}, fmt.Errorf("sinble boolean encoded byte should be of form 0x80 or 0x00")
+ }
+ return MakeBool(boolValue), nil
+ case Byte:
+ if len(valueByte) != 1 {
+ return Value{}, fmt.Errorf("byte should be length 1")
+ }
+ return MakeByte(valueByte[0]), nil
+ case ArrayStatic:
+ childT := make([]Type, valueType.staticLength)
+ for i := 0; i < int(valueType.staticLength); i++ {
+ childT[i] = valueType.childTypes[0]
+ }
+ converted, err := MakeTupleType(childT)
+ if err != nil {
+ return Value{}, err
+ }
+ tupleDecoded, err := tupleDecoding(valueByte, converted)
+ if err != nil {
+ return Value{}, err
+ }
+ tupleDecoded.ABIType = valueType
+ return tupleDecoded, nil
+ case Address:
+ if len(valueByte) != addressByteSize {
+ return Value{}, fmt.Errorf("address should be length 32")
+ }
+ var byteAssign [addressByteSize]byte
+ copy(byteAssign[:], valueByte)
+ return MakeAddress(byteAssign), nil
+ case ArrayDynamic:
+ if len(valueByte) < lengthEncodeByteSize {
+ return Value{}, fmt.Errorf("dynamic array format corrupted")
+ }
+ dynamicLen := binary.BigEndian.Uint16(valueByte[:lengthEncodeByteSize])
+ childT := make([]Type, dynamicLen)
+ for i := 0; i < int(dynamicLen); i++ {
+ childT[i] = valueType.childTypes[0]
+ }
+ converted, err := MakeTupleType(childT)
+ if err != nil {
+ return Value{}, err
+ }
+ tupleDecoded, err := tupleDecoding(valueByte[lengthEncodeByteSize:], converted)
+ if err != nil {
+ return Value{}, err
+ }
+ tupleDecoded.ABIType = valueType
+ return tupleDecoded, nil
+ case String:
+ if len(valueByte) < lengthEncodeByteSize {
+ return Value{}, fmt.Errorf("string format corrupted")
+ }
+ stringLenBytes := valueByte[:lengthEncodeByteSize]
+ byteLen := binary.BigEndian.Uint16(stringLenBytes)
+ if len(valueByte[lengthEncodeByteSize:]) != int(byteLen) {
+ return Value{}, fmt.Errorf("string representation in byte: length not matching")
+ }
+ return MakeString(string(valueByte[lengthEncodeByteSize:])), nil
+ case Tuple:
+ return tupleDecoding(valueByte, valueType)
+ default:
+ return Value{}, fmt.Errorf("decode: unknown type error")
+ }
+}
+
+// tupleDecoding takes a byte string and an ABI tuple type,
+// and decodes the bytes into an ABI tuple value.
+func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) {
+ dynamicSegments := make([]segment, 0)
+ valuePartition := make([][]byte, 0)
+ iterIndex := 0
+
+ for i := 0; i < len(valueType.childTypes); i++ {
+ if valueType.childTypes[i].IsDynamic() {
+ if len(valueBytes[iterIndex:]) < lengthEncodeByteSize {
+ return Value{}, fmt.Errorf("ill formed tuple dynamic typed value encoding")
+ }
+ dynamicIndex := binary.BigEndian.Uint16(valueBytes[iterIndex : iterIndex+lengthEncodeByteSize])
+ if len(dynamicSegments) > 0 {
+ dynamicSegments[len(dynamicSegments)-1].right = int(dynamicIndex)
+ }
+ // we know where encoded bytes for dynamic value start, but we do not know where it ends
+ // unless we see the start of the next encoded bytes for dynamic value
+ dynamicSegments = append(dynamicSegments, segment{
+ left: int(dynamicIndex),
+ right: -1,
+ })
+ valuePartition = append(valuePartition, nil)
+ iterIndex += lengthEncodeByteSize
+ } else {
+ // if bool ...
+ if valueType.childTypes[i].abiTypeID == Bool {
+ // search previous bool
+ before := findBoolLR(valueType.childTypes, i, -1)
+ // search after bool
+ after := findBoolLR(valueType.childTypes, i, 1)
+ if before%8 == 0 {
+ if after > 7 {
+ after = 7
+ }
+ // parse bool in a byte to multiple byte strings
+ for boolIndex := uint(0); boolIndex <= uint(after); boolIndex++ {
+ boolMask := 0x80 >> boolIndex
+ if valueBytes[iterIndex]&byte(boolMask) > 0 {
+ valuePartition = append(valuePartition, []byte{0x80})
+ } else {
+ valuePartition = append(valuePartition, []byte{0x00})
+ }
+ }
+ i += after
+ iterIndex++
+ } else {
+ return Value{}, fmt.Errorf("expected before bool number mod 8 == 0")
+ }
+ } else {
+ // not bool ...
+ currLen, err := valueType.childTypes[i].ByteLen()
+ if err != nil {
+ return Value{}, err
+ }
+ valuePartition = append(valuePartition, valueBytes[iterIndex:iterIndex+currLen])
+ iterIndex += currLen
+ }
+ }
+ if i != len(valueType.childTypes)-1 && iterIndex >= len(valueBytes) {
+ return Value{}, fmt.Errorf("input byte not enough to decode")
+ }
+ }
+ if len(dynamicSegments) > 0 {
+ dynamicSegments[len(dynamicSegments)-1].right = len(valueBytes)
+ iterIndex = len(valueBytes)
+ }
+ if iterIndex < len(valueBytes) {
+ return Value{}, fmt.Errorf("input byte not fully consumed")
+ }
+
+ // check segment indices are valid
+ // if the dynamic segment are not consecutive and well-ordered, we return error
+ for index, seg := range dynamicSegments {
+ if seg.left > seg.right {
+ return Value{}, fmt.Errorf("dynamic segment should display a [l, r] space with l <= r")
+ }
+ if index != len(dynamicSegments)-1 && seg.right != dynamicSegments[index+1].left {
+ return Value{}, fmt.Errorf("dynamic segment should be consecutive")
+ }
+ }
+
+ segIndex := 0
+ for i := 0; i < len(valueType.childTypes); i++ {
+ if valueType.childTypes[i].IsDynamic() {
+ valuePartition[i] = valueBytes[dynamicSegments[segIndex].left:dynamicSegments[segIndex].right]
+ segIndex++
+ }
+ }
+
+ // decode each tuple element bytes
+ values := make([]Value, 0)
+ for i := 0; i < len(valueType.childTypes); i++ {
+ valueTi, err := Decode(valuePartition[i], valueType.childTypes[i])
+ if err != nil {
+ return Value{}, err
+ }
+ values = append(values, valueTi)
+ }
+ return Value{
+ ABIType: valueType,
+ value: values,
+ }, nil
+}
diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go
new file mode 100644
index 0000000000..22d37c5531
--- /dev/null
+++ b/data/abi/abi_encode_test.go
@@ -0,0 +1,1203 @@
+// 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 .
+
+package abi
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "math/big"
+ "testing"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/chrismcguire/gobberish"
+ "github.com/stretchr/testify/require"
+)
+
+func TestEncodeValid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // encoding test for uint type, iterating through all uint sizes
+ // randomly pick 1000 valid uint values and check if encoded value match with expected
+ for intSize := 8; intSize <= 512; intSize += 8 {
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize))
+ for i := 0; i < 1000; i++ {
+ randomInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ randomIntByte := randomInt.Bytes()
+ expected := make([]byte, intSize/8-len(randomIntByte))
+ expected = append(expected, randomIntByte...)
+
+ uintValue, err := MakeUint(randomInt, uint16(intSize))
+ require.NoError(t, err, "makeUint Fail")
+ uintBytesActual, err := uintValue.Encode()
+
+ require.NoError(t, err, "uint encode fail")
+ require.Equal(t, expected, uintBytesActual, "encode uint not match with expected")
+ }
+ // 2^[bitSize] - 1 test
+ // check if uint can contain max uint value (2^bitSize - 1)
+ largest := big.NewInt(0).Add(
+ upperLimit,
+ big.NewInt(1).Neg(big.NewInt(1)),
+ )
+ valueLargest, err := MakeUint(largest, uint16(intSize))
+ require.NoError(t, err, "make largest uint fail")
+ encoded, err := valueLargest.Encode()
+ require.NoError(t, err, "largest uint encode error")
+ require.Equal(t, largest.Bytes(), encoded, "encode uint largest do not match with expected")
+ }
+
+ // encoding test for ufixed, iterating through all the valid ufixed bitSize and precision
+ // randomly generate 10 big int values for ufixed numerator and check if encoded value match with expected
+ // also check if ufixed can fit max numerator (2^bitSize - 1) under specific byte bitSize
+ for size := 8; size <= 512; size += 8 {
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size))
+ largest := big.NewInt(0).Add(
+ upperLimit,
+ big.NewInt(1).Neg(big.NewInt(1)),
+ )
+ for precision := 1; precision <= 160; precision++ {
+ for i := 0; i < 10; i++ {
+ randomInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ valueUfixed, err := MakeUfixed(randomInt, uint16(size), uint16(precision))
+ require.NoError(t, err, "makeUfixed Fail")
+
+ encodedUfixed, err := valueUfixed.Encode()
+ require.NoError(t, err, "ufixed encode fail")
+
+ randomBytes := randomInt.Bytes()
+ buffer := make([]byte, size/8-len(randomBytes))
+ buffer = append(buffer, randomBytes...)
+ require.Equal(t, buffer, encodedUfixed, "encode ufixed not match with expected")
+ }
+ // (2^[bitSize] - 1) / (10^[precision]) test
+ ufixedLargestValue, err := MakeUfixed(largest, uint16(size), uint16(precision))
+ require.NoError(t, err, "make largest ufixed fail")
+ ufixedLargestEncode, err := ufixedLargestValue.Encode()
+ require.NoError(t, err, "largest ufixed encode error")
+ require.Equal(t, largest.Bytes(), ufixedLargestEncode,
+ "encode ufixed largest do not match with expected")
+ }
+ }
+
+ // encoding test for address, since address is 32 byte, it can be considered as 256 bit uint
+ // randomly generate 1000 uint256 and make address values, check if encoded value match with expected
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), 256)
+ for i := 0; i < 1000; i++ {
+ randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ rand256Bytes := randomAddrInt.Bytes()
+ addrBytesExpected := make([]byte, 32-len(rand256Bytes))
+ addrBytesExpected = append(addrBytesExpected, rand256Bytes...)
+
+ var addrBytes [32]byte
+ copy(addrBytes[:], addrBytesExpected[:32])
+
+ addressValue := MakeAddress(addrBytes)
+ addrBytesActual, err := addressValue.Encode()
+ require.NoError(t, err, "address encode fail")
+ require.Equal(t, addrBytesExpected, addrBytesActual, "encode addr not match with expected")
+ }
+
+ // encoding test for bool values
+ for i := 0; i < 2; i++ {
+ boolValue := MakeBool(i == 1)
+ boolEncode, err := boolValue.Encode()
+ require.NoError(t, err, "bool encode fail")
+ expected := []byte{0x00}
+ if i == 1 {
+ expected = []byte{0x80}
+ }
+ require.Equal(t, expected, boolEncode, "encode bool not match with expected")
+ }
+
+ // encoding test for byte values
+ for i := 0; i < (1 << 8); i++ {
+ byteValue := MakeByte(byte(i))
+ byteEncode, err := byteValue.Encode()
+ require.NoError(t, err, "byte encode fail")
+ expected := []byte{byte(i)}
+ require.Equal(t, expected, byteEncode, "encode byte not match with expected")
+ }
+
+ // encoding test for string values, since strings in ABI contain utf-8 symbols
+ // we use `gobberish` to generate random utf-8 symbols
+ // randomly generate utf-8 str from length 1 to 100, each length draw 10 random strs
+ // check if encoded ABI str match with expected value
+ for length := 1; length <= 100; length++ {
+ for i := 0; i < 10; i++ {
+ // generate utf8 strings from `gobberish` at some length
+ utf8Str := gobberish.GenerateString(length)
+ strValue := MakeString(utf8Str)
+ // since string is just type alias of `byte[]`, we need to store number of bytes in encoding
+ utf8ByteLen := len([]byte(utf8Str))
+ lengthBytes := make([]byte, 2)
+ binary.BigEndian.PutUint16(lengthBytes, uint16(utf8ByteLen))
+ expected := append(lengthBytes, []byte(utf8Str)...)
+
+ strEncode, err := strValue.Encode()
+ require.NoError(t, err, "string encode fail")
+ require.Equal(t, expected, strEncode, "encode string not match with expected")
+ }
+ }
+
+ // encoding test for static bool array, the expected behavior of encoding is to
+ // compress multiple bool into a single byte.
+ // input: {T, F, F, T, T}, encode expected: {0b10011000}
+ t.Run("static bool array encoding", func(t *testing.T) {
+ inputBase := []bool{true, false, false, true, true}
+ arrayElems := make([]Value, len(inputBase))
+ for index, bVal := range inputBase {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected := []byte{
+ 0b10011000,
+ }
+ boolArr, err := MakeStaticArray(arrayElems)
+ require.NoError(t, err, "make static array should not return error")
+ boolArrEncode, err := boolArr.Encode()
+ require.NoError(t, err, "static bool array encoding should not return error")
+ require.Equal(t, expected, boolArrEncode, "static bool array encode not match expected")
+ })
+
+ // encoding test for static bool array
+ // input: {F, F, F, T, T, F, T, F, T, F, T}, encode expected: {0b00011010, 0b10100000}
+ t.Run("static bool array encoding", func(t *testing.T) {
+ inputBase := []bool{false, false, false, true, true, false, true, false, true, false, true}
+ arrayElems := make([]Value, len(inputBase))
+ for index, bVal := range inputBase {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected := []byte{
+ 0b00011010, 0b10100000,
+ }
+ boolArr, err := MakeStaticArray(arrayElems)
+ require.NoError(t, err, "make static array should not return error")
+ boolArrEncode, err := boolArr.Encode()
+ require.NoError(t, err, "static bool array encoding should not return error")
+ require.Equal(t, expected, boolArrEncode, "static bool array encode not match expected")
+ })
+
+ // encoding test for dynamic bool array
+ // input: {F, T, F, T, F, T, F, T, F, T}, encode expected: {0b01010101, 0b01000000}
+ t.Run("dynamic bool array encoding", func(t *testing.T) {
+ inputBase := []bool{false, true, false, true, false, true, false, true, false, true}
+ arrayElems := make([]Value, len(inputBase))
+ for index, bVal := range inputBase {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected := []byte{
+ 0x00, 0x0A, 0b01010101, 0b01000000,
+ }
+ boolArr, err := MakeDynamicArray(arrayElems, MakeBoolType())
+ require.NoError(t, err, "make dynamic array should not return error")
+ boolArrEncode, err := boolArr.Encode()
+ require.NoError(t, err, "dynamic bool array encoding should not return error")
+ require.Equal(t, expected, boolArrEncode, "dynamic bool array encode not match expected")
+ })
+
+ // encoding test for dynamic tuple values
+ // input type: (string, bool, bool, bool, bool, string)
+ // input value: ("ABC", T, F, T, F, "DEF")
+ /*
+ encode expected:
+ 0x00, 0x05 (first string start at 5th byte)
+ 0b10100000 (4 bool tuple element compacted together)
+ 0x00, 0x0A (second string start at 10th byte)
+ 0x00, 0x03 (first string byte length 3)
+ byte('A'), byte('B'), byte('C') (first string encoded bytes)
+ 0x00, 0x03 (second string byte length 3)
+ byte('D'), byte('E'), byte('F') (second string encoded bytes)
+ */
+ t.Run("dynamic tuple encoding", func(t *testing.T) {
+ inputBase := []interface{}{
+ "ABC", true, false, true, false, "DEF",
+ }
+ tupleElems := make([]Value, len(inputBase))
+ // make tuple element values
+ for index, bVal := range inputBase {
+ temp, ok := bVal.(string)
+ if ok {
+ tupleElems[index] = MakeString(temp)
+ } else {
+ temp := bVal.(bool)
+ tupleElems[index] = MakeBool(temp)
+ }
+ }
+ expected := []byte{
+ 0x00, 0x05, 0b10100000, 0x00, 0x0A,
+ 0x00, 0x03, byte('A'), byte('B'), byte('C'),
+ 0x00, 0x03, byte('D'), byte('E'), byte('F'),
+ }
+ stringTuple, err := MakeTuple(tupleElems)
+ require.NoError(t, err, "make string tuple should not return error")
+ stringTupleEncode, err := stringTuple.Encode()
+ require.NoError(t, err, "string tuple encoding should not return error")
+ require.Equal(t, expected, stringTupleEncode, "string tuple encoding not match expected")
+ })
+
+ // encoding test for tuples with static bool arrays
+ // input type: {bool[2], bool[2]}
+ // input value: ({T, T}, {T, T})
+ /*
+ encode expected:
+ 0b11000000 (first static bool array)
+ 0b11000000 (second static bool array)
+ */
+ t.Run("static bool array tuple encoding", func(t *testing.T) {
+ boolArr := []bool{true, true}
+ boolValArr := make([]Value, 2)
+ for i := 0; i < 2; i++ {
+ boolValArr[i] = MakeBool(boolArr[i])
+ }
+ boolArrVal, err := MakeStaticArray(boolValArr)
+ require.NoError(t, err, "make bool static array should not return error")
+ tupleVal, err := MakeTuple([]Value{boolArrVal, boolArrVal})
+ require.NoError(t, err, "make tuple value should not return error")
+ expected := []byte{
+ 0b11000000,
+ 0b11000000,
+ }
+ actual, err := tupleVal.Encode()
+ require.NoError(t, err, "encode tuple value should not return error")
+ require.Equal(t, expected, actual, "encode static bool tuple should be equal")
+ })
+
+ // encoding test for tuples with static and dynamic bool arrays
+ // input type: (bool[2], bool[])
+ // input value: ({T, T}, {T, T})
+ /*
+ encode expected:
+ 0b11000000 (first static bool array)
+ 0x00, 0x03 (second dynamic bool array starts at 3rd byte)
+ 0x00, 0x02 (dynamic bool array length 2)
+ 0b11000000 (second static bool array)
+ */
+ t.Run("static/dynamic bool array tuple encoding", func(t *testing.T) {
+ boolArr := []bool{true, true}
+ boolValArr := make([]Value, 2)
+ for i := 0; i < 2; i++ {
+ boolValArr[i] = MakeBool(boolArr[i])
+ }
+ boolArrStaticVal, err := MakeStaticArray(boolValArr)
+ require.NoError(t, err, "make static bool array should not return error")
+ boolArrDynamicVal, err := MakeDynamicArray(boolValArr, MakeBoolType())
+ require.NoError(t, err, "make dynamic bool array should not return error")
+ tupleVal, err := MakeTuple([]Value{boolArrStaticVal, boolArrDynamicVal})
+ require.NoError(t, err, "make tuple for static/dynamic bool array should not return error")
+ expected := []byte{
+ 0b11000000,
+ 0x00, 0x03,
+ 0x00, 0x02, 0b11000000,
+ }
+ actual, err := tupleVal.Encode()
+ require.NoError(t, err, "tuple value encoding should not return error")
+ require.Equal(t, expected, actual, "encode static/dynamic bool array tuple should not return error")
+ })
+
+ // encoding test for tuples with all dynamic bool arrays
+ // input type: (bool[], bool[])
+ // input values: ({}, {})
+ /*
+ encode expected:
+ 0x00, 0x04 (first dynamic bool array starts at 4th byte)
+ 0x00, 0x06 (second dynamic bool array starts at 6th byte)
+ 0x00, 0x00 (first dynamic bool array length 0)
+ 0x00, 0x00 (second dynamic bool array length 0)
+ */
+ t.Run("empty dynamic array tuple encoding", func(t *testing.T) {
+ emptyDynamicArray, err := MakeDynamicArray([]Value{}, MakeBoolType())
+ require.NoError(t, err, "make empty dynamic array should not return error")
+ tupleVal, err := MakeTuple([]Value{emptyDynamicArray, emptyDynamicArray})
+ require.NoError(t, err, "make empty dynamic array tuple should not return error")
+ expected := []byte{
+ 0x00, 0x04, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+ actual, err := tupleVal.Encode()
+ require.NoError(t, err, "encode empty dynamic array tuple should not return error")
+ require.Equal(t, expected, actual, "encode empty dynamic array tuple does not match with expected")
+ })
+
+ // encoding test for empty tuple
+ // input: (), expected encoding: ""
+ t.Run("empty tuple encoding", func(t *testing.T) {
+ emptyTuple, err := MakeTuple([]Value{})
+ require.NoError(t, err, "make empty tuple should not return error")
+ expected := make([]byte, 0)
+ actual, err := emptyTuple.Encode()
+ require.NoError(t, err, "encode empty tuple should not return error")
+ require.Equal(t, expected, actual, "empty tuple encode should not return error")
+ })
+}
+
+func TestDecodeValid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // decoding test for uint, iterating through all valid uint bitSize
+ // randomly take 1000 tests on each valid bitSize
+ // generate bytes from random uint values and decode bytes with additional type information
+ for intSize := 8; intSize <= 512; intSize += 8 {
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize))
+ for i := 0; i < 1000; i++ {
+ randomInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+ expected, err := MakeUint(randomInt, uint16(intSize))
+ require.NoError(t, err, "makeUint Fail")
+ encodedUint, err := expected.Encode()
+ require.NoError(t, err, "uint encode fail")
+ // attempt to decode from given bytes: encodedUint
+ uintType, err := MakeUintType(uint16(intSize))
+ require.NoError(t, err, "uint type make fail")
+ actual, err := Decode(encodedUint, uintType)
+ require.NoError(t, err, "decoding uint should not return error")
+ require.Equal(t, expected, actual, "decode uint fail to match expected value")
+ }
+ }
+
+ // decoding test for ufixed, iterating through all valid ufixed bitSize and precision
+ // randomly take 10 tests on each valid setting
+ // generate ufixed bytes and try to decode back with additional type information
+ for size := 8; size <= 512; size += 8 {
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size))
+ for precision := 1; precision <= 160; precision++ {
+ for i := 0; i < 10; i++ {
+ randomInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ valueUfixed, err := MakeUfixed(randomInt, uint16(size), uint16(precision))
+ require.NoError(t, err, "makeUfixed Fail")
+
+ encodedUfixed, err := valueUfixed.Encode()
+ require.NoError(t, err, "ufixed encode fail")
+
+ ufixedType, err := MakeUfixedType(uint16(size), uint16(precision))
+ require.NoError(t, err, "ufixed type make fail")
+
+ decodedUfixed, err := Decode(encodedUfixed, ufixedType)
+ require.NoError(t, err, "decoding ufixed should not return error")
+ require.Equal(t, valueUfixed, decodedUfixed, "decode ufixed fail to match expected value")
+ }
+ }
+ }
+
+ // decoding test for address, randomly take 1000 tests
+ // address is type alias of byte[32], we generate address value with random 256 bit big int values
+ // we make the expected address value and decode the encoding of expected, check if they match
+ upperLimit := big.NewInt(0).Lsh(big.NewInt(1), 256)
+ for i := 0; i < 1000; i++ {
+ randomAddrInt, err := rand.Int(rand.Reader, upperLimit)
+ require.NoError(t, err, "cryptographic random int init fail")
+
+ addressBytes := randomAddrInt.Bytes()
+ address := make([]byte, 32-len(addressBytes))
+ address = append(address, addressBytes...)
+
+ var addrBytes [32]byte
+ copy(addrBytes[:], address[:32])
+
+ addressValue := MakeAddress(addrBytes)
+ addrEncode, err := addressValue.Encode()
+ require.NoError(t, err, "address encode fail")
+
+ addressDecoded, err := Decode(addrEncode, MakeAddressType())
+ require.NoError(t, err, "decoding address should not return error")
+ require.Equal(t, addressValue, addressDecoded, "decode addr not match with expected")
+ }
+
+ // bool value decoding test
+ for i := 0; i < 2; i++ {
+ boolValue := MakeBool(i == 1)
+ boolEncode, err := boolValue.Encode()
+ require.NoError(t, err, "bool encode fail")
+ boolDecode, err := Decode(boolEncode, MakeBoolType())
+ require.NoError(t, err, "decoding bool should not return error")
+ require.Equal(t, boolValue, boolDecode, "decode bool not match with expected")
+ }
+
+ // byte value decoding test, iterating through 256 valid byte value
+ for i := 0; i < (1 << 8); i++ {
+ byteValue := MakeByte(byte(i))
+ byteEncode, err := byteValue.Encode()
+ require.NoError(t, err, "byte encode fail")
+ byteDecode, err := Decode(byteEncode, MakeByteType())
+ require.NoError(t, err, "decoding byte should not return error")
+ require.Equal(t, byteValue, byteDecode, "decode byte not match with expected")
+ }
+
+ // string value decoding test, test from utf string length 1 to 100
+ // randomly take 10 utf-8 strings to make ABI string values
+ // decode the encoded expected value and check if they match
+ for length := 1; length <= 100; length++ {
+ for i := 0; i < 10; i++ {
+ utf8Str := gobberish.GenerateString(length)
+ strValue := MakeString(utf8Str)
+ strEncode, err := strValue.Encode()
+ require.NoError(t, err, "string encode fail")
+ strDecode, err := Decode(strEncode, MakeStringType())
+ require.NoError(t, err, "decoding string should not return error")
+ require.Equal(t, strValue, strDecode, "encode string not match with expected")
+ }
+ }
+
+ // decoding test for static bool array
+ // expected value: bool[5]: {T, F, F, T, T}
+ // input: 0b10011000
+ t.Run("static bool array decode", func(t *testing.T) {
+ inputBase := []bool{true, false, false, true, true}
+ arrayElems := make([]Value, len(inputBase))
+ for index, bVal := range inputBase {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected, err := MakeStaticArray(arrayElems)
+ require.NoError(t, err, "make expected value should not return error")
+ actual, err := Decode(
+ []byte{0b10011000},
+ MakeStaticArrayType(MakeBoolType(), uint16(len(inputBase))),
+ )
+ require.NoError(t, err, "decoding static bool array should not return error")
+ require.Equal(t, expected, actual, "static bool array decode do not match expected")
+ })
+
+ // decoding test for static bool array
+ // expected value: bool[11]: F, F, F, T, T, F, T, F, T, F, T
+ // input: 0b00011010, 0b10100000
+ t.Run("static bool array decode", func(t *testing.T) {
+ inputBase := []bool{false, false, false, true, true, false, true, false, true, false, true}
+ arrayElems := make([]Value, len(inputBase))
+ for index, bVal := range inputBase {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected, err := MakeStaticArray(arrayElems)
+ require.NoError(t, err, "make expected value should not return error")
+ actual, err := Decode(
+ []byte{
+ 0b00011010, 0b10100000,
+ },
+ MakeStaticArrayType(MakeBoolType(), uint16(len(inputBase))),
+ )
+ require.NoError(t, err, "decoding static bool array should not return error")
+ require.Equal(t, expected, actual, "static bool array decode do not match expected")
+ })
+
+ // decoding test for static uint array
+ // expected input: uint64[8]: {1, 2, 3, 4, 5, 6, 7, 8}
+ /*
+ input: 0, 0, 0, 0, 0, 0, 0, 1 (encoding for uint64 1)
+ 0, 0, 0, 0, 0, 0, 0, 2 (encoding for uint64 2)
+ 0, 0, 0, 0, 0, 0, 0, 3 (encoding for uint64 3)
+ 0, 0, 0, 0, 0, 0, 0, 4 (encoding for uint64 4)
+ 0, 0, 0, 0, 0, 0, 0, 5 (encoding for uint64 5)
+ 0, 0, 0, 0, 0, 0, 0, 6 (encoding for uint64 6)
+ 0, 0, 0, 0, 0, 0, 0, 7 (encoding for uint64 7)
+ 0, 0, 0, 0, 0, 0, 0, 8 (encoding for uint64 8)
+ */
+ t.Run("static uint array decode", func(t *testing.T) {
+ inputUint := []uint64{1, 2, 3, 4, 5, 6, 7, 8}
+ arrayElems := make([]Value, len(inputUint))
+ for index, uintVal := range inputUint {
+ arrayElems[index] = MakeUint64(uintVal)
+ }
+ uintT, err := MakeUintType(64)
+ require.NoError(t, err, "make uint64 type should not return error")
+ expected, err := MakeStaticArray(arrayElems)
+ require.NoError(t, err, "make uint64 static array should not return error")
+ arrayEncoded, err := expected.Encode()
+ require.NoError(t, err, "uint64 static array encode should not return error")
+ arrayDecoded, err := Decode(arrayEncoded, MakeStaticArrayType(uintT, uint16(len(inputUint))))
+ require.NoError(t, err, "uint64 static array decode should not return error")
+ require.Equal(t, expected, arrayDecoded, "uint64 static array decode do not match with expected value")
+ })
+
+ // decoding test for dynamic bool array
+ // expected value: bool[]: {F, T, F, T, F, T, F, T, F, T}
+ /*
+ input bytes: 0x00, 0x0A (dynamic bool array length 10)
+ 0b01010101, 0b01000000 (dynamic bool array encoding)
+ */
+ t.Run("dynamic bool array decode", func(t *testing.T) {
+ inputBool := []bool{false, true, false, true, false, true, false, true, false, true}
+ arrayElems := make([]Value, len(inputBool))
+ for index, bVal := range inputBool {
+ arrayElems[index] = MakeBool(bVal)
+ }
+ expected, err := MakeDynamicArray(arrayElems, MakeBoolType())
+ require.NoError(t, err, "make expected value should not return error")
+ inputEncoded := []byte{
+ 0x00, 0x0A, 0b01010101, 0b01000000,
+ }
+ actual, err := Decode(inputEncoded, MakeDynamicArrayType(MakeBoolType()))
+ require.NoError(t, err, "decode dynamic array should not return error")
+ require.Equal(t, expected, actual, "decode dynamic array do not match expected")
+ })
+
+ // decoding test for dynamic tuple values
+ // expected value type: (string, bool, bool, bool, bool, string)
+ // expected value: ("ABC", T, F, T, F, "DEF")
+ /*
+ input bytes:
+ 0x00, 0x05 (first string start at 5th byte)
+ 0b10100000 (4 bool tuple element compacted together)
+ 0x00, 0x0A (second string start at 10th byte)
+ 0x00, 0x03 (first string byte length 3)
+ byte('A'), byte('B'), byte('C') (first string encoded bytes)
+ 0x00, 0x03 (second string byte length 3)
+ byte('D'), byte('E'), byte('F') (second string encoded bytes)
+ */
+ t.Run("dynamic tuple decoding", func(t *testing.T) {
+ inputEncode := []byte{
+ 0x00, 0x05, 0b10100000, 0x00, 0x0A,
+ 0x00, 0x03, byte('A'), byte('B'), byte('C'),
+ 0x00, 0x03, byte('D'), byte('E'), byte('F'),
+ }
+ expectedBase := []interface{}{
+ "ABC", true, false, true, false, "DEF",
+ }
+ tupleElems := make([]Value, len(expectedBase))
+ for index, bVal := range expectedBase {
+ temp, ok := bVal.(string)
+ if ok {
+ tupleElems[index] = MakeString(temp)
+ } else {
+ temp := bVal.(bool)
+ tupleElems[index] = MakeBool(temp)
+ }
+ }
+ expected, err := MakeTuple(tupleElems)
+ require.NoError(t, err, "make expected value should not return error")
+ actual, err := Decode(
+ inputEncode,
+ Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeStringType(),
+ MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(),
+ MakeStringType(),
+ },
+ staticLength: 6,
+ },
+ )
+ require.NoError(t, err, "decoding dynamic tuple should not return error")
+ require.Equal(t, expected, actual, "dynamic tuple not match with expected")
+ })
+
+ // decoding test for tuple with static bool array
+ // expected type: (bool[2], bool[2])
+ // expected value: ({T, T}, {T, T})
+ /*
+ input bytes:
+ 0b11000000 (first static bool array)
+ 0b11000000 (second static bool array)
+ */
+ t.Run("static bool array tuple decoding", func(t *testing.T) {
+ boolArr := []bool{true, true}
+ boolValArr := make([]Value, 2)
+ for i := 0; i < 2; i++ {
+ boolValArr[i] = MakeBool(boolArr[i])
+ }
+ boolArrVal, err := MakeStaticArray(boolValArr)
+ require.NoError(t, err, "make bool static array should not return error")
+ tupleVal, err := MakeTuple([]Value{boolArrVal, boolArrVal})
+ require.NoError(t, err, "make tuple value should not return error")
+ encodedInput := []byte{
+ 0b11000000,
+ 0b11000000,
+ }
+ decoded, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ })
+ require.NoError(t, err, "decode tuple value should not return error")
+ require.Equal(t, tupleVal, decoded, "decoded tuple value do not match with expected")
+ })
+
+ // decoding test for tuple with static and dynamic bool array
+ // expected type: (bool[2], bool[])
+ // expected value: ({T, T}, {T, T})
+ /*
+ input bytes:
+ 0b11000000 (first static bool array)
+ 0x00, 0x03 (second dynamic bool array starts at 3rd byte)
+ 0x00, 0x02 (dynamic bool array length 2)
+ 0b11000000 (second static bool array)
+ */
+ t.Run("static/dynamic bool array tuple decoding", func(t *testing.T) {
+ boolArr := []bool{true, true}
+ boolValArr := make([]Value, 2)
+ for i := 0; i < 2; i++ {
+ boolValArr[i] = MakeBool(boolArr[i])
+ }
+ boolArrStaticVal, err := MakeStaticArray(boolValArr)
+ require.NoError(t, err, "make static bool array should not return error")
+ boolArrDynamicVal, err := MakeDynamicArray(boolValArr, MakeBoolType())
+ require.NoError(t, err, "make dynamic bool array should not return error")
+ tupleVal, err := MakeTuple([]Value{boolArrStaticVal, boolArrDynamicVal})
+ require.NoError(t, err, "make tuple for static/dynamic bool array should not return error")
+ encodedInput := []byte{
+ 0b11000000,
+ 0x00, 0x03,
+ 0x00, 0x02, 0b11000000,
+ }
+ decoded, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ })
+ require.NoError(t, err, "decode tuple for static/dynamic bool array should not return error")
+ require.Equal(t, tupleVal, decoded, "decoded tuple value do not match with expected")
+ })
+
+ // decoding test for tuple with all dynamic bool array
+ // expected value: (bool[], bool[])
+ // expected value: ({}, {})
+ /*
+ input bytes:
+ 0x00, 0x04 (first dynamic bool array starts at 4th byte)
+ 0x00, 0x06 (second dynamic bool array starts at 6th byte)
+ 0x00, 0x00 (first dynamic bool array length 0)
+ 0x00, 0x00 (second dynamic bool array length 0)
+ */
+ t.Run("empty dynamic array tuple decoding", func(t *testing.T) {
+ emptyDynamicArray, err := MakeDynamicArray([]Value{}, MakeBoolType())
+ require.NoError(t, err, "make empty dynamic array should not return error")
+ tupleVal, err := MakeTuple([]Value{emptyDynamicArray, emptyDynamicArray})
+ require.NoError(t, err, "make empty dynamic array tuple should not return error")
+ encodedInput := []byte{
+ 0x00, 0x04, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+ decoded, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ })
+ require.NoError(t, err, "decode tuple for empty dynamic array should not return error")
+ require.Equal(t, tupleVal, decoded, "decoded tuple value do not match with expected")
+ })
+
+ // decoding test for empty tuple
+ // expected value: ()
+ // byte input: ""
+ t.Run("empty tuple decoding", func(t *testing.T) {
+ emptyTuple, err := MakeTuple([]Value{})
+ require.NoError(t, err, "make empty tuple should not return error")
+ encodedInput := make([]byte, 0)
+ decoded, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 0,
+ childTypes: []Type{},
+ })
+ require.NoError(t, err, "decode empty tuple should not return error")
+ require.Equal(t, emptyTuple, decoded, "empty tuple encode should not return error")
+ })
+}
+
+func TestDecodeInvalid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // decoding test for *corrupted* static bool array
+ // expected 9 elements for static bool array
+ // encoded bytes have only 8 bool values
+ // should throw error
+ t.Run("corrupted static bool array decode", func(t *testing.T) {
+ inputBase := []byte{0b11111111}
+ arrayType := MakeStaticArrayType(MakeBoolType(), 9)
+ _, err := Decode(inputBase, arrayType)
+ require.Error(t, err, "decoding corrupted static bool array should return error")
+ })
+
+ // decoding test for *corrupted* static bool array
+ // expected 8 elements for static bool array
+ // encoded bytes have 1 byte more (0b00000000)
+ // should throw error
+ t.Run("corrupted static bool array decode", func(t *testing.T) {
+ inputBase := []byte{0b01001011, 0b00000000}
+ arrayType := MakeStaticArrayType(MakeBoolType(), 8)
+ _, err := Decode(inputBase, arrayType)
+ require.Error(t, err, "decoding corrupted static bool array should return error")
+ })
+
+ // decoding test for *corrupted* static uint array
+ // expected 8 uint elements in static uint64[8] array
+ // encoded bytes provide only 7 uint64 encoding
+ // should throw error
+ t.Run("static uint array decode", func(t *testing.T) {
+ inputBase := []byte{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ }
+ uintT, err := MakeUintType(64)
+ require.NoError(t, err, "make uint64 type should not return error")
+ uintTArray := MakeStaticArrayType(uintT, 8)
+ _, err = Decode(inputBase, uintTArray)
+ require.Error(t, err, "corrupted uint64 static array decode should return error")
+ })
+
+ // decoding test for *corrupted* static uint array
+ // expected 7 uint elements in static uint64[7] array
+ // encoded bytes provide 8 uint64 encoding (one more uint64: 7)
+ // should throw error
+ t.Run("static uint array decode", func(t *testing.T) {
+ inputBase := []byte{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 0, 0, 0, 0, 6,
+ 0, 0, 0, 0, 0, 0, 0, 7,
+ }
+ uintT, err := MakeUintType(64)
+ require.NoError(t, err, "make uint64 type should not return error")
+ uintTArray := MakeStaticArrayType(uintT, 7)
+ _, err = Decode(inputBase, uintTArray)
+ require.Error(t, err, "corrupted uint64 static array decode should return error")
+ })
+
+ // decoding test for *corrupted* dynamic bool array
+ // expected 0x0A (10) bool elements in encoding head
+ // encoded bytes provide only 8 bool elements
+ // should throw error
+ t.Run("corrupted dynamic bool array decode", func(t *testing.T) {
+ inputBase := []byte{
+ 0x00, 0x0A, 0b10101010,
+ }
+ dynamicT := MakeDynamicArrayType(MakeBoolType())
+ _, err := Decode(inputBase, dynamicT)
+ require.Error(t, err, "decode corrupted dynamic array should return error")
+ })
+
+ // decoding test for *corrupted* dynamic bool array
+ // expected 0x07 (7) bool elements in encoding head
+ // encoded bytes provide 1 byte more (0b00000000)
+ // should throw error
+ t.Run("corrupted dynamic bool array decode", func(t *testing.T) {
+ inputBase := []byte{
+ 0x00, 0x07, 0b10101010, 0b00000000,
+ }
+ dynamicT := MakeDynamicArrayType(MakeBoolType())
+ _, err := Decode(inputBase, dynamicT)
+ require.Error(t, err, "decode corrupted dynamic array should return error")
+ })
+
+ // decoding test for *corrupted* dynamic tuple value
+ // expected type: (string, bool, bool, bool, bool, string)
+ // expected value: ("ABC", T, F, T, F, "DEF")
+ /*
+ corrupted bytes:
+ 0x00, 0x04 (corrupted: first string start at 4th byte, should be 5th)
+ 0b10100000 (4 bool tuple element compacted together)
+ 0x00, 0x0A (second string start at 10th byte)
+ 0x00, 0x03 (first string byte length 3)
+ byte('A'), byte('B'), byte('C') (first string encoded bytes)
+ 0x00, 0x03 (second string byte length 3)
+ byte('D'), byte('E'), byte('F') (second string encoded bytes)
+ */
+ // the result would be: first string have length 0x0A, 0x00
+ // the length exceeds the segment it allocated: 0x0A, 0x00, 0x03, byte('A'), byte('B'), byte('C')
+ // should throw error
+ t.Run("corrupted dynamic tuple decoding", func(t *testing.T) {
+ inputEncode := []byte{
+ 0x00, 0x04, 0b10100000, 0x00, 0x0A,
+ 0x00, 0x03, byte('A'), byte('B'), byte('C'),
+ 0x00, 0x03, byte('D'), byte('E'), byte('F'),
+ }
+ expectedBase := []interface{}{
+ "ABC", true, false, true, false, "DEF",
+ }
+ tupleElems := make([]Value, len(expectedBase))
+ for index, bVal := range expectedBase {
+ temp, ok := bVal.(string)
+ if ok {
+ tupleElems[index] = MakeString(temp)
+ } else {
+ temp := bVal.(bool)
+ tupleElems[index] = MakeBool(temp)
+ }
+ }
+ _, err := Decode(
+ inputEncode,
+ Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeStringType(),
+ MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(),
+ MakeStringType(),
+ },
+ },
+ )
+ require.Error(t, err, "corrupted decoding dynamic tuple should return error")
+ })
+
+ // decoding test for *corrupted* tuple with static bool arrays
+ // expected type: (bool[2], bool[2])
+ // expected value: ({T, T}, {T, T})
+ /*
+ corrupted bytes test case 0:
+ 0b11000000
+ 0b11000000
+ 0b00000000 <- corrupted byte, 1 byte more
+
+ corrupted bytes test case 0:
+ 0b11000000
+ <- corrupted byte, 1 byte missing
+ */
+ t.Run("corrupted static bool array tuple decoding", func(t *testing.T) {
+ expectedType := Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ }
+
+ encodedInput0 := []byte{
+ 0b11000000,
+ 0b11000000,
+ 0b00000000,
+ }
+ _, err := Decode(encodedInput0, expectedType)
+ require.Error(t, err, "decode corrupted tuple value should return error")
+
+ encodedInput1 := []byte{
+ 0b11000000,
+ }
+ _, err = Decode(encodedInput1, expectedType)
+ require.Error(t, err, "decode corrupted tuple value should return error")
+ })
+
+ // decoding test for *corrupted* tuple with static and dynamic bool array
+ // expected type: (bool[2], bool[])
+ // expected value: ({T, T}, {T, T})
+ /*
+ corrupted bytes:
+ 0b11000000 (first static bool array)
+ 0x03 <- corrupted, missing 0x00 byte (second dynamic bool array starts at 3rd byte)
+ 0x00, 0x02 (dynamic bool array length 2)
+ 0b11000000 (second static bool array)
+ */
+ t.Run("corrupted static/dynamic bool array tuple decoding", func(t *testing.T) {
+ encodedInput := []byte{
+ 0b11000000,
+ 0x03,
+ 0x00, 0x02, 0b11000000,
+ }
+ _, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayStatic,
+ staticLength: 2,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ })
+ require.Error(t, err, "decode corrupted tuple for static/dynamic bool array should return error")
+ })
+
+ // decoding test for *corrupted* tuple with dynamic bool array
+ // expected type: (bool[], bool[])
+ // expected value: ({}, {})
+ /*
+ corrupted bytes:
+ 0x00, 0x04 (first dynamic bool array starts at 4th byte)
+ 0x00, 0x07 <- corrupted, should be 0x06 (second dynamic bool array starts at 6th byte)
+ 0x00, 0x00 (first dynamic bool array length 0)
+ 0x00, 0x00 (second dynamic bool array length 0)
+
+ first dynamic array starts at 0x04, segment is 0x00, 0x00, 0x00, 1 byte 0x00 more
+ second dynamic array starts at 0x07, and only have 0x00 1 byte
+ */
+ // should return error
+ t.Run("corrupted empty dynamic array tuple decoding", func(t *testing.T) {
+ encodedInput := []byte{
+ 0x00, 0x04, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+ _, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 2,
+ childTypes: []Type{
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ {
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{MakeBoolType()},
+ },
+ },
+ })
+ require.Error(t, err, "decode corrupted tuple for empty dynamic array should return error")
+ })
+
+ // decoding test for *corrupted* empty tuple
+ // expected value: ()
+ // corrupted input: 0xFF, should be empty byte
+ // should return error
+ t.Run("corrupted empty tuple decoding", func(t *testing.T) {
+ encodedInput := []byte{0xFF}
+ _, err := Decode(encodedInput, Type{
+ abiTypeID: Tuple,
+ staticLength: 0,
+ childTypes: []Type{},
+ })
+ require.Error(t, err, "decode corrupted empty tuple should return error")
+ })
+}
+
+func generateStaticArray(t *testing.T, testValuePool *[][]Value) {
+ // int
+ for intIndex := 0; intIndex < len((*testValuePool)[Uint]); intIndex += 200 {
+ staticArrayList := make([]Value, 20)
+ for i := 0; i < 20; i++ {
+ staticArrayList[i] = (*testValuePool)[Uint][intIndex+i]
+ }
+ staticArray, err := MakeStaticArray(staticArrayList)
+ require.NoError(t, err, "make static array for uint should not return error")
+ (*testValuePool)[ArrayStatic] = append((*testValuePool)[ArrayStatic], staticArray)
+ }
+ // byte
+ byteArrayList := make([]Value, 20)
+ for byteIndex := 0; byteIndex < 20; byteIndex++ {
+ byteArrayList[byteIndex] = (*testValuePool)[Byte][byteIndex]
+ }
+ byteStaticArray, err := MakeStaticArray(byteArrayList)
+ require.NoError(t, err, "make static array for byte should not return error")
+ (*testValuePool)[ArrayStatic] = append((*testValuePool)[ArrayStatic], byteStaticArray)
+ // address
+ addressArrayList := make([]Value, 20)
+ for addrIndex := 0; addrIndex < 20; addrIndex++ {
+ addressArrayList[addrIndex] = (*testValuePool)[Address][addrIndex]
+ }
+ addressStaticArray, err := MakeStaticArray(addressArrayList)
+ require.NoError(t, err, "make static array for address should not return error")
+ (*testValuePool)[ArrayStatic] = append((*testValuePool)[ArrayStatic], addressStaticArray)
+ // string
+ stringArrayList := make([]Value, 20)
+ for strIndex := 0; strIndex < 20; strIndex++ {
+ stringArrayList[strIndex] = (*testValuePool)[String][strIndex]
+ }
+ stringStaticArray, err := MakeStaticArray(stringArrayList)
+ require.NoError(t, err, "make static array for string should not return error")
+ (*testValuePool)[ArrayStatic] = append((*testValuePool)[ArrayStatic], stringStaticArray)
+ // bool
+ boolArrayList := make([]Value, 20)
+ for boolIndex := 0; boolIndex < 20; boolIndex++ {
+ valBig, err := rand.Int(rand.Reader, big.NewInt(2))
+ require.NoError(t, err, "generate random bool index should not return error")
+ valIndex := valBig.Int64()
+ boolArrayList[boolIndex] = (*testValuePool)[Bool][valIndex]
+ }
+ boolStaticArray, err := MakeStaticArray(boolArrayList)
+ require.NoError(t, err, "make static array for bool should not return error")
+ (*testValuePool)[ArrayStatic] = append((*testValuePool)[ArrayStatic], boolStaticArray)
+}
+
+func generateDynamicArray(t *testing.T, testValuePool *[][]Value) {
+ // int
+ for intIndex := 0; intIndex < len((*testValuePool)[Uint]); intIndex += 200 {
+ dynamicArrayList := make([]Value, 20)
+ for i := 0; i < 20; i++ {
+ dynamicArrayList[i] = (*testValuePool)[Uint][intIndex+i]
+ }
+ dynamicArray, err := MakeDynamicArray(dynamicArrayList, dynamicArrayList[0].ABIType)
+ require.NoError(t, err, "make static array for uint should not return error")
+ (*testValuePool)[ArrayDynamic] = append((*testValuePool)[ArrayDynamic], dynamicArray)
+ }
+ // byte
+ byteArrayList := make([]Value, 20)
+ for byteIndex := 0; byteIndex < 20; byteIndex++ {
+ byteArrayList[byteIndex] = (*testValuePool)[Byte][byteIndex]
+ }
+ byteDynamicArray, err := MakeDynamicArray(byteArrayList, byteArrayList[0].ABIType)
+ require.NoError(t, err, "make dynamic array for byte should not return error")
+ (*testValuePool)[ArrayDynamic] = append((*testValuePool)[ArrayDynamic], byteDynamicArray)
+ // address
+ addressArrayList := make([]Value, 20)
+ for addrIndex := 0; addrIndex < 20; addrIndex++ {
+ addressArrayList[addrIndex] = (*testValuePool)[Address][addrIndex]
+ }
+ addressDynamicArray, err := MakeDynamicArray(addressArrayList, MakeAddressType())
+ require.NoError(t, err, "make dynamic array for address should not return error")
+ (*testValuePool)[ArrayDynamic] = append((*testValuePool)[ArrayDynamic], addressDynamicArray)
+ // string
+ stringArrayList := make([]Value, 20)
+ for strIndex := 0; strIndex < 20; strIndex++ {
+ stringArrayList[strIndex] = (*testValuePool)[String][strIndex]
+ }
+ stringDynamicArray, err := MakeDynamicArray(stringArrayList, MakeStringType())
+ require.NoError(t, err, "make dynamic array for string should not return error")
+ (*testValuePool)[ArrayDynamic] = append((*testValuePool)[ArrayDynamic], stringDynamicArray)
+ // bool
+ boolArrayList := make([]Value, 20)
+ for boolIndex := 0; boolIndex < 20; boolIndex++ {
+ valBig, err := rand.Int(rand.Reader, big.NewInt(2))
+ require.NoError(t, err, "generate random bool index should not return error")
+ valIndex := valBig.Int64()
+ boolArrayList[boolIndex] = (*testValuePool)[Bool][valIndex]
+ }
+ boolDynamicArray, err := MakeDynamicArray(boolArrayList, MakeBoolType())
+ require.NoError(t, err, "make dynamic array for bool should not return error")
+ (*testValuePool)[ArrayDynamic] = append((*testValuePool)[ArrayDynamic], boolDynamicArray)
+}
+
+func generateTuples(t *testing.T, testValuePool *[][]Value, slotRange int) {
+ for i := 0; i < 100; i++ {
+ tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(2))
+ require.NoError(t, err, "generate random tuple length should not return error")
+ tupleLen := 1 + tupleLenBig.Int64()
+ tupleValList := make([]Value, tupleLen)
+ for tupleElemIndex := 0; tupleElemIndex < int(tupleLen); tupleElemIndex++ {
+ tupleTypeIndexBig, err := rand.Int(rand.Reader, big.NewInt(int64(slotRange)))
+ require.NoError(t, err, "generate random tuple element type index should not return error")
+ tupleTypeIndex := tupleTypeIndexBig.Int64()
+ tupleElemChoiceRange := len((*testValuePool)[tupleTypeIndex])
+
+ tupleElemRangeIndexBig, err := rand.Int(rand.Reader, big.NewInt(int64(tupleElemChoiceRange)))
+ require.NoError(t, err, "generate random tuple element index in test pool should not return error")
+ tupleElemRangeIndex := tupleElemRangeIndexBig.Int64()
+ tupleElem := (*testValuePool)[tupleTypeIndex][tupleElemRangeIndex]
+ tupleValList[tupleElemIndex] = tupleElem
+ }
+ tupleVal, err := MakeTuple(tupleValList)
+ require.NoError(t, err, "make tuple should not return error")
+ (*testValuePool)[Tuple] = append((*testValuePool)[Tuple], tupleVal)
+ }
+}
+
+// round-trip test for random tuple elements
+// first we generate base type elements to each slot of testValuePool
+// then we generate static/dynamic array based on the pre-generated random values
+// we generate base tuples based on base-type elements/static arrays/dynamic arrays
+// we also generate cascaded tuples (tuples with tuple elements)
+func TestEncodeDecodeRandomTuple(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // test pool for 9 distinct types
+ testValuePool := make([][]Value, 9)
+ for i := 8; i <= 512; i += 8 {
+ max := big.NewInt(1).Lsh(big.NewInt(1), uint(i))
+ for j := 0; j < 200; j++ {
+ randVal, err := rand.Int(rand.Reader, max)
+ require.NoError(t, err, "generate largest number bound, should be no error")
+ uintTemp, err := MakeUint(randVal, uint16(i))
+ require.NoError(t, err, "generate random ABI uint should not return error")
+ testValuePool[Uint] = append(testValuePool[Uint], uintTemp)
+ }
+ for j := 1; j < 160; j++ {
+ randVal, err := rand.Int(rand.Reader, max)
+ require.NoError(t, err, "generate largest number bound, should be no error")
+ ufixedTemp, err := MakeUfixed(randVal, uint16(i), uint16(j))
+ require.NoError(t, err, "generate random ABI ufixed should not return error")
+ testValuePool[Ufixed] = append(testValuePool[Ufixed], ufixedTemp)
+ }
+ }
+ for i := 0; i < (1 << 8); i++ {
+ testValuePool[Byte] = append(testValuePool[Byte], MakeByte(byte(i)))
+ }
+ for i := 0; i < 2; i++ {
+ testValuePool[Bool] = append(testValuePool[Bool], MakeBool(i == 1))
+ }
+ for i := 0; i < 500; i++ {
+ max := big.NewInt(1).Lsh(big.NewInt(1), 256)
+ randVal, err := rand.Int(rand.Reader, max)
+ require.NoError(t, err, "generate largest number bound, should be no error")
+ addrBytes := randVal.Bytes()
+ remainBytes := make([]byte, 32-len(addrBytes))
+ addrBytes = append(remainBytes, addrBytes...)
+ var addrBytesToMake [32]byte
+ copy(addrBytesToMake[:], addrBytes)
+ testValuePool[Address] = append(testValuePool[Address], MakeAddress(addrBytesToMake))
+ }
+ for i := 1; i <= 100; i++ {
+ for j := 0; j < 4; j++ {
+ abiString := MakeString(gobberish.GenerateString(i))
+ testValuePool[String] = append(testValuePool[String], abiString)
+ }
+ }
+ // Array static
+ generateStaticArray(t, &testValuePool)
+ // Array dynamic
+ generateDynamicArray(t, &testValuePool)
+ // tuple generation
+ generateTuples(t, &testValuePool, 8)
+ // generate cascaded tuples
+ generateTuples(t, &testValuePool, 9)
+ // test tuple encode-decode round-trip
+ for _, tuple := range testValuePool[Tuple] {
+ t.Run("random tuple encode-decode test", func(t *testing.T) {
+ encoded, err := tuple.Encode()
+ require.NoError(t, err, "encode tuple should not have error")
+ decoded, err := Decode(encoded, tuple.ABIType)
+ require.NoError(t, err, "decode tuple should not have error")
+ require.Equal(t, tuple, decoded, "encoded-decoded tuple should match with expected")
+ })
+ }
+}
diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go
new file mode 100644
index 0000000000..65e3dfc6af
--- /dev/null
+++ b/data/abi/abi_type.go
@@ -0,0 +1,491 @@
+// 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 .
+
+package abi
+
+import (
+ "fmt"
+ "math"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+/*
+ ABI-Types: uint: An N-bit unsigned integer (8 <= N <= 512 and N % 8 = 0).
+ | byte (alias for uint8)
+ | ufixed x (8 <= N <= 512, N % 8 = 0, and 0 < M <= 160)
+ | bool
+ | address (alias for byte[32])
+ | []
+ | []
+ | 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 ([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 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 `(, ..., )` 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 or `ufixed` bitSize
+ bitSize uint16
+ // only can be applied to `ufixed` precision
+ 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 "uint" + strconv.Itoa(int(t.bitSize))
+ case Byte:
+ return "byte"
+ case Ufixed:
+ return "ufixed" + strconv.Itoa(int(t.bitSize)) + "x" + strconv.Itoa(int(t.precision))
+ case Bool:
+ return "bool"
+ case ArrayStatic:
+ return t.childTypes[0].String() + "[" + strconv.Itoa(int(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.Regexp = nil
+var ufixedRegexp *regexp.Regexp = nil
+
+func init() {
+ var err error
+ // Note that we allow only decimal static array length
+ staticArrayRegexp, err = regexp.Compile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`)
+ if err != nil {
+ panic(err.Error())
+ }
+ ufixedRegexp, err = regexp.Compile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`)
+ if err != nil {
+ panic(err.Error())
+ }
+}
+
+// TypeFromString de-serialize ABI type from a string following ABI encoding.
+func TypeFromString(str string) (Type, error) {
+ switch {
+ case strings.HasSuffix(str, "[]"):
+ arrayArgType, err := TypeFromString(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 := TypeFromString(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(uint16(typeSize))
+ case str == "byte":
+ return MakeByteType(), 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(uint16(ufixedSize), uint16(ufixedPrecision))
+ case str == "bool":
+ return MakeBoolType(), nil
+ case str == "address":
+ return MakeAddressType(), nil
+ case str == "string":
+ return MakeStringType(), 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 := TypeFromString(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 uint16) (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: typeSize,
+ }, nil
+}
+
+// MakeByteType makes `Byte` ABI type.
+func MakeByteType() Type {
+ return Type{
+ abiTypeID: Byte,
+ }
+}
+
+// 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 uint16, typePrecision uint16) (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: typeSize,
+ precision: typePrecision,
+ }, nil
+}
+
+// MakeBoolType makes `Bool` ABI type.
+func MakeBoolType() Type {
+ return Type{
+ abiTypeID: Bool,
+ }
+}
+
+// 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,
+ }
+}
+
+// MakeAddressType makes `Address` ABI type.
+func MakeAddressType() Type {
+ return Type{
+ abiTypeID: Address,
+ }
+}
+
+// MakeDynamicArrayType makes dynamic length array by taking array element type as argument.
+func MakeDynamicArrayType(argumentType Type) Type {
+ return Type{
+ abiTypeID: ArrayDynamic,
+ childTypes: []Type{argumentType},
+ }
+}
+
+// MakeStringType makes `String` ABI type.
+func MakeStringType() Type {
+ return Type{
+ abiTypeID: String,
+ }
+}
+
+// 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) / 8
+ if t.staticLength%8 != 0 {
+ byteLen++
+ }
+ 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 / 8
+ if boolNum%8 != 0 {
+ size++
+ }
+ } 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())
+ }
+}
diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go
new file mode 100644
index 0000000000..136ecb8cf7
--- /dev/null
+++ b/data/abi/abi_type_test.go
@@ -0,0 +1,613 @@
+// 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 .
+
+package abi
+
+import (
+ "fmt"
+ "math/rand"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMakeTypeValid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // uint
+ for i := 8; i <= 512; i += 8 {
+ uintType, err := MakeUintType(uint16(i))
+ require.NoError(t, err, "make uint type in valid space should not return error")
+ expected := "uint" + strconv.Itoa(i)
+ actual := uintType.String()
+ require.Equal(t, expected, actual, "MakeUintType: expected %s, actual %s", expected, actual)
+ }
+ // ufixed
+ for i := 8; i <= 512; i += 8 {
+ for j := 1; j <= 160; j++ {
+ ufixedType, err := MakeUfixedType(uint16(i), uint16(j))
+ require.NoError(t, err, "make ufixed type in valid space should not return error")
+ expected := "ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j)
+ actual := ufixedType.String()
+ require.Equal(t, expected, actual,
+ "TypeFromString ufixed error: expected %s, actual %s", expected, actual)
+ }
+ }
+ // bool/strings/address/byte + dynamic/static array + tuple
+ var testcases = []struct {
+ input Type
+ testType string
+ expected string
+ }{
+ {input: MakeBoolType(), testType: "bool", expected: "bool"},
+ {input: MakeStringType(), testType: "string", expected: "string"},
+ {input: MakeAddressType(), testType: "address", expected: "address"},
+ {input: MakeByteType(), testType: "byte", expected: "byte"},
+ // dynamic array
+ {
+ input: MakeDynamicArrayType(
+ Type{
+ abiTypeID: Uint,
+ bitSize: uint16(32),
+ },
+ ),
+ testType: "dynamic array",
+ expected: "uint32[]",
+ },
+ {
+ input: MakeDynamicArrayType(
+ MakeDynamicArrayType(
+ MakeByteType(),
+ ),
+ ),
+ testType: "dynamic array",
+ expected: "byte[][]",
+ },
+ {
+ input: MakeStaticArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: uint16(128),
+ precision: uint16(10),
+ },
+ uint16(100),
+ ),
+ testType: "static array",
+ expected: "ufixed128x10[100]",
+ },
+ {
+ input: MakeStaticArrayType(
+ MakeStaticArrayType(
+ MakeBoolType(),
+ uint16(128),
+ ),
+ uint16(256),
+ ),
+ testType: "static array",
+ expected: "bool[128][256]",
+ },
+ // tuple type
+ {
+ input: Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ {
+ abiTypeID: Uint,
+ bitSize: uint16(32),
+ },
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeAddressType(),
+ MakeByteType(),
+ MakeStaticArrayType(MakeBoolType(), uint16(10)),
+ MakeDynamicArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: uint16(256),
+ precision: uint16(10),
+ },
+ ),
+ },
+ staticLength: 4,
+ },
+ MakeDynamicArrayType(MakeByteType()),
+ },
+ staticLength: 3,
+ },
+ testType: "tuple type",
+ expected: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])",
+ },
+ }
+ for _, testcase := range testcases {
+ t.Run(fmt.Sprintf("MakeType test %s", testcase.testType), func(t *testing.T) {
+ actual := testcase.input.String()
+ require.Equal(t, testcase.expected, actual,
+ "MakeType: expected %s, actual %s", testcase.expected, actual)
+ })
+ }
+}
+
+func TestMakeTypeInvalid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // uint
+ for i := 0; i <= 1000; i++ {
+ randInput := rand.Uint32() % (1 << 16)
+ for randInput%8 == 0 && randInput <= 512 && randInput >= 8 {
+ randInput = rand.Uint32() % (1 << 16)
+ }
+ // note: if a var mod 8 = 0 (or not) in uint32, then it should mod 8 = 0 (or not) in uint16.
+ _, err := MakeUintType(uint16(randInput))
+ require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", uint16(randInput))
+ }
+ // ufixed
+ for i := 0; i <= 10000; i++ {
+ randSize := rand.Uint64() % (1 << 16)
+ for randSize%8 == 0 && randSize <= 512 && randSize >= 8 {
+ randSize = rand.Uint64() % (1 << 16)
+ }
+ randPrecision := rand.Uint32()
+ for randPrecision >= 1 && randPrecision <= 160 {
+ randPrecision = rand.Uint32()
+ }
+ _, err := MakeUfixedType(uint16(randSize), uint16(randPrecision))
+ require.Error(t, err, "MakeUfixedType: should throw error on bitSize %d, precision %d", randSize, randPrecision)
+ }
+}
+
+func TestTypeFromStringValid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // uint
+ for i := 8; i <= 512; i += 8 {
+ expected, err := MakeUintType(uint16(i))
+ require.NoError(t, err, "make uint type in valid space should not return error")
+ actual, err := TypeFromString(expected.String())
+ require.NoError(t, err, "TypeFromString: uint parsing error: %s", expected.String())
+ require.Equal(t, expected, actual,
+ "TypeFromString: expected %s, actual %s", expected.String(), actual.String())
+ }
+ // ufixed
+ for i := 8; i <= 512; i += 8 {
+ for j := 1; j <= 160; j++ {
+ expected, err := MakeUfixedType(uint16(i), uint16(j))
+ require.NoError(t, err, "make ufixed type in valid space should not return error")
+ actual, err := TypeFromString("ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j))
+ require.NoError(t, err, "TypeFromString ufixed parsing error: %s", expected.String())
+ require.Equal(t, expected, actual,
+ "TypeFromString ufixed: expected %s, actual %s", expected.String(), actual.String())
+ }
+ }
+ var testcases = []struct {
+ input string
+ testType string
+ expected Type
+ }{
+ {input: MakeBoolType().String(), testType: "bool", expected: MakeBoolType()},
+ {input: MakeStringType().String(), testType: "string", expected: MakeStringType()},
+ {input: MakeAddressType().String(), testType: "address", expected: MakeAddressType()},
+ {input: MakeByteType().String(), testType: "byte", expected: MakeByteType()},
+ {
+ input: "uint256[]",
+ testType: "dynamic array",
+ expected: MakeDynamicArrayType(Type{abiTypeID: Uint, bitSize: 256}),
+ },
+ {
+ input: "ufixed256x64[]",
+ testType: "dynamic array",
+ expected: MakeDynamicArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: 256,
+ precision: 64,
+ },
+ ),
+ },
+ {
+ input: "byte[][][][]",
+ testType: "dynamic array",
+ expected: MakeDynamicArrayType(
+ MakeDynamicArrayType(
+ MakeDynamicArrayType(
+ MakeDynamicArrayType(
+ MakeByteType(),
+ ),
+ ),
+ ),
+ ),
+ },
+ // static array
+ {
+ input: "address[100]",
+ testType: "static array",
+ expected: MakeStaticArrayType(
+ MakeAddressType(),
+ uint16(100),
+ ),
+ },
+ {
+ input: "uint64[][200]",
+ testType: "static array",
+ expected: MakeStaticArrayType(
+ MakeDynamicArrayType(
+ Type{abiTypeID: Uint, bitSize: uint16(64)},
+ ),
+ uint16(200),
+ ),
+ },
+ // tuple type
+ {
+ input: "()",
+ testType: "tuple type",
+ expected: Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{},
+ staticLength: 0,
+ },
+ },
+ {
+ input: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])",
+ testType: "tuple type",
+ expected: Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ {
+ abiTypeID: Uint,
+ bitSize: uint16(32),
+ },
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeAddressType(),
+ MakeByteType(),
+ MakeStaticArrayType(MakeBoolType(), uint16(10)),
+ MakeDynamicArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: uint16(256),
+ precision: uint16(10),
+ },
+ ),
+ },
+ staticLength: 4,
+ },
+ MakeDynamicArrayType(MakeByteType()),
+ },
+ staticLength: 3,
+ },
+ },
+ {
+ input: "(uint32,(address,byte,bool[10],(ufixed256x10[])))",
+ testType: "tuple type",
+ expected: Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ {
+ abiTypeID: Uint,
+ bitSize: uint16(32),
+ },
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeAddressType(),
+ MakeByteType(),
+ MakeStaticArrayType(MakeBoolType(), uint16(10)),
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeDynamicArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: uint16(256),
+ precision: uint16(10),
+ },
+ ),
+ },
+ staticLength: 1,
+ },
+ },
+ staticLength: 4,
+ },
+ },
+ staticLength: 2,
+ },
+ },
+ {
+ input: "((uint32),(address,(byte,bool[10],ufixed256x10[])))",
+ testType: "tuple type",
+ expected: Type{
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ {
+ abiTypeID: Uint,
+ bitSize: uint16(32),
+ },
+ },
+ staticLength: 1,
+ },
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeAddressType(),
+ {
+ abiTypeID: Tuple,
+ childTypes: []Type{
+ MakeByteType(),
+ MakeStaticArrayType(MakeBoolType(), uint16(10)),
+ MakeDynamicArrayType(
+ Type{
+ abiTypeID: Ufixed,
+ bitSize: uint16(256),
+ precision: uint16(10),
+ },
+ ),
+ },
+ staticLength: 3,
+ },
+ },
+ staticLength: 2,
+ },
+ },
+ staticLength: 2,
+ },
+ },
+ }
+ for _, testcase := range testcases {
+ t.Run(fmt.Sprintf("TypeFromString test %s", testcase.testType), func(t *testing.T) {
+ actual, err := TypeFromString(testcase.input)
+ require.NoError(t, err, "TypeFromString %s parsing error", testcase.testType)
+ require.Equal(t, testcase.expected, actual, "TestFromString %s: expected %s, actual %s",
+ testcase.testType, testcase.expected.String(), actual.String())
+ })
+ }
+}
+
+func TestTypeFromStringInvalid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ for i := 0; i <= 1000; i++ {
+ randSize := rand.Uint64()
+ for randSize%8 == 0 && randSize <= 512 && randSize >= 8 {
+ randSize = rand.Uint64()
+ }
+ errorInput := "uint" + strconv.FormatUint(randSize, 10)
+ _, err := TypeFromString(errorInput)
+ require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize)
+ }
+ for i := 0; i <= 10000; i++ {
+ randSize := rand.Uint64()
+ for randSize%8 == 0 && randSize <= 512 && randSize >= 8 {
+ randSize = rand.Uint64()
+ }
+ randPrecision := rand.Uint64()
+ for randPrecision >= 1 && randPrecision <= 160 {
+ randPrecision = rand.Uint64()
+ }
+ errorInput := "ufixed" + strconv.FormatUint(randSize, 10) + "x" + strconv.FormatUint(randPrecision, 10)
+ _, err := TypeFromString(errorInput)
+ require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize)
+ }
+ var testcases = []string{
+ // uint
+ "uint123x345",
+ "uint 128",
+ "uint8 ",
+ "uint!8",
+ "uint[32]",
+ "uint-893",
+ "uint#120\\",
+ // ufixed
+ "ufixed000000000016x0000010",
+ "ufixed123x345",
+ "ufixed 128 x 100",
+ "ufixed64x10 ",
+ "ufixed!8x2 ",
+ "ufixed[32]x16",
+ "ufixed-64x+100",
+ "ufixed16x+12",
+ // dynamic array
+ "uint256 []",
+ "byte[] ",
+ "[][][]",
+ "stuff[]",
+ // static array
+ "ufixed32x10[0]",
+ "byte[10 ]",
+ "uint64[0x21]",
+ // tuple
+ "(ufixed128x10))",
+ "(,uint128,byte[])",
+ "(address,ufixed64x5,)",
+ "(byte[16],somethingwrong)",
+ "( )",
+ "((uint32)",
+ "(byte,,byte)",
+ "((byte),,(byte))",
+ }
+ for _, testcase := range testcases {
+ t.Run(fmt.Sprintf("TypeFromString dynamic array test %s", testcase), func(t *testing.T) {
+ _, err := TypeFromString(testcase)
+ require.Error(t, err, "%s should throw error", testcase)
+ })
+ }
+}
+
+func generateTupleType(baseTypes []Type, tupleTypes []Type) Type {
+ if len(baseTypes) == 0 && len(tupleTypes) == 0 {
+ panic("should not pass all nil arrays into generateTupleType")
+ }
+ tupleLen := 0
+ for tupleLen == 0 {
+ tupleLen = rand.Intn(20)
+ }
+ resultTypes := make([]Type, tupleLen)
+ for i := 0; i < tupleLen; i++ {
+ baseOrTuple := rand.Intn(5)
+ if baseOrTuple == 1 && len(tupleTypes) > 0 {
+ resultTypes[i] = tupleTypes[rand.Intn(len(tupleTypes))]
+ } else {
+ resultTypes[i] = baseTypes[rand.Intn(len(baseTypes))]
+ }
+ }
+ return Type{abiTypeID: Tuple, childTypes: resultTypes, staticLength: uint16(tupleLen)}
+}
+
+func TestTypeMISC(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ rand.Seed(time.Now().Unix())
+
+ var testpool = []Type{
+ MakeBoolType(),
+ MakeAddressType(),
+ MakeStringType(),
+ MakeByteType(),
+ }
+ for i := 8; i <= 512; i += 8 {
+ uintT, err := MakeUintType(uint16(i))
+ require.NoError(t, err, "make uint type error")
+ testpool = append(testpool, uintT)
+ }
+ for i := 8; i <= 512; i += 8 {
+ for j := 1; j <= 160; j++ {
+ ufixedT, err := MakeUfixedType(uint16(i), uint16(j))
+ require.NoError(t, err, "make ufixed type error: bitSize %d, precision %d", i, j)
+ testpool = append(testpool, ufixedT)
+ }
+ }
+ for _, testcase := range testpool {
+ testpool = append(testpool, MakeDynamicArrayType(testcase))
+ testpool = append(testpool, MakeStaticArrayType(testcase, 10))
+ testpool = append(testpool, MakeStaticArrayType(testcase, 20))
+ }
+
+ for _, testcase := range testpool {
+ require.True(t, testcase.Equal(testcase), "test type self equal error")
+ }
+ baseTestCount := 0
+ for baseTestCount < 1000 {
+ index0 := rand.Intn(len(testpool))
+ index1 := rand.Intn(len(testpool))
+ if index0 == index1 {
+ continue
+ }
+ require.False(t, testpool[index0].Equal(testpool[index1]),
+ "test type not equal error\n%s\n%s",
+ testpool[index0].String(), testpool[index1].String())
+ baseTestCount++
+ }
+
+ testpoolTuple := make([]Type, 0)
+ for i := 0; i < 100; i++ {
+ testpoolTuple = append(testpoolTuple, generateTupleType(testpool, testpoolTuple))
+ }
+ for _, testcaseTuple := range testpoolTuple {
+ require.True(t, testcaseTuple.Equal(testcaseTuple), "test type tuple equal error")
+ }
+
+ tupleTestCount := 0
+ for tupleTestCount < 100 {
+ index0 := rand.Intn(len(testpoolTuple))
+ index1 := rand.Intn(len(testpoolTuple))
+ if testpoolTuple[index0].String() == testpoolTuple[index1].String() {
+ continue
+ }
+ require.False(t, testpoolTuple[index0].Equal(testpoolTuple[index1]),
+ "test type tuple not equal error\n%s\n%s",
+ testpoolTuple[index0].String(), testpoolTuple[index1].String())
+ tupleTestCount++
+ }
+
+ testpool = append(testpool, testpoolTuple...)
+ isDynamicCount := 0
+ for isDynamicCount < 100 {
+ index := rand.Intn(len(testpool))
+ isDynamicArr := strings.Contains(testpool[index].String(), "[]")
+ isDynamicStr := strings.Contains(testpool[index].String(), "string")
+ require.Equal(t, isDynamicArr || isDynamicStr, testpool[index].IsDynamic(),
+ "test type isDynamic error\n%s", testpool[index].String())
+ isDynamicCount++
+ }
+
+ addressByteLen, err := MakeAddressType().ByteLen()
+ require.NoError(t, err, "address type bytelen should not return error")
+ require.Equal(t, 32, addressByteLen, "address type bytelen should be 32")
+ byteByteLen, err := MakeByteType().ByteLen()
+ require.NoError(t, err, "byte type bytelen should not return error")
+ require.Equal(t, 1, byteByteLen, "byte type bytelen should be 1")
+ boolByteLen, err := MakeBoolType().ByteLen()
+ require.NoError(t, err, "bool type bytelen should be 1")
+ require.Equal(t, 1, boolByteLen, "bool type bytelen should be 1")
+
+ byteLenTestCount := 0
+ for byteLenTestCount < 100 {
+ index := rand.Intn(len(testpool))
+ testType := testpool[index]
+ byteLen, err := testType.ByteLen()
+ if testType.IsDynamic() {
+ require.Error(t, err, "byteLen test error on %s dynamic type, should have error",
+ testType.String())
+ } else {
+ require.NoError(t, err, "byteLen test error on %s dynamic type, should not have error")
+ if testType.abiTypeID == Tuple {
+ sizeSum := 0
+ for i := 0; i < len(testType.childTypes); i++ {
+ if testType.childTypes[i].abiTypeID == Bool {
+ // search previous bool
+ before := findBoolLR(testType.childTypes, i, -1)
+ // search after bool
+ after := findBoolLR(testType.childTypes, i, 1)
+ // append to heads and tails
+ require.True(t, before%8 == 0, "expected tuple bool compact by 8")
+ if after > 7 {
+ after = 7
+ }
+ i += after
+ sizeSum++
+ } else {
+ childByteSize, err := testType.childTypes[i].ByteLen()
+ require.NoError(t, err, "byteLen not expected to fail on tuple child type")
+ sizeSum += childByteSize
+ }
+ }
+
+ require.Equal(t, sizeSum, byteLen,
+ "%s do not match calculated byte length %d", testType.String(), sizeSum)
+ } else if testType.abiTypeID == ArrayStatic {
+ if testType.childTypes[0].abiTypeID == Bool {
+ expected := testType.staticLength / 8
+ if testType.staticLength%8 != 0 {
+ expected++
+ }
+ actual, err := testType.ByteLen()
+ require.NoError(t, err, "%s should not return error on byteLen test")
+ require.Equal(t, int(expected), actual, "%s do not match calculated byte length %d",
+ testType.String(), expected)
+ } else {
+ childSize, err := testType.childTypes[0].ByteLen()
+ require.NoError(t, err, "%s should not return error on byteLen test", testType.childTypes[0].String())
+ expected := childSize * int(testType.staticLength)
+ require.Equal(t, expected, byteLen,
+ "%s do not match calculated byte length %d", testType.String(), expected)
+ }
+ }
+ }
+ byteLenTestCount++
+ }
+}
diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go
new file mode 100644
index 0000000000..9f72ba755d
--- /dev/null
+++ b/data/abi/abi_value.go
@@ -0,0 +1,313 @@
+// 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 .
+
+package abi
+
+import (
+ "fmt"
+ "math"
+ "math/big"
+)
+
+// Value struct is the ABI Value, holding ABI Type information and the ABI value representation.
+type Value struct {
+ ABIType Type
+ value interface{}
+}
+
+// MakeUint8 takes a go `uint8` and gives an ABI Value of ABI type `uint8`.
+func MakeUint8(value uint8) Value {
+ bigInt := big.NewInt(int64(value))
+ res, _ := MakeUint(bigInt, 8)
+ return res
+}
+
+// MakeUint16 takes a go `uint16` and gives an ABI Value of ABI type `uint16`.
+func MakeUint16(value uint16) Value {
+ bigInt := big.NewInt(int64(value))
+ res, _ := MakeUint(bigInt, 16)
+ return res
+}
+
+// MakeUint32 takes a go `uint32` and gives an ABI Value of ABI type `uint32`.
+func MakeUint32(value uint32) Value {
+ bigInt := big.NewInt(int64(value))
+ res, _ := MakeUint(bigInt, 32)
+ return res
+}
+
+// MakeUint64 takes a go `uint64` and gives an ABI Value of ABI type `uint64`.
+func MakeUint64(value uint64) Value {
+ bigInt := new(big.Int).SetUint64(value)
+ res, _ := MakeUint(bigInt, 64)
+ return res
+}
+
+// MakeUint takes a big integer representation and a type bitSize,
+// and returns an ABI Value of ABI Uint type.
+func MakeUint(value *big.Int, size uint16) (Value, error) {
+ typeUint, err := MakeUintType(size)
+ if err != nil {
+ return Value{}, err
+ }
+ upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(size))
+ if value.Cmp(upperLimit) >= 0 {
+ return Value{}, fmt.Errorf("passed value larger than uint bitSize %d", size)
+ }
+ return Value{
+ ABIType: typeUint,
+ value: value,
+ }, nil
+}
+
+// MakeUfixed takes a big integer representation, a type bitSize, and a type precision,
+// and returns an ABI Value of ABI UFixedx
+func MakeUfixed(value *big.Int, size uint16, precision uint16) (Value, error) {
+ ufixedValueType, err := MakeUfixedType(size, precision)
+ if err != nil {
+ return Value{}, err
+ }
+ uintVal, err := MakeUint(value, size)
+ if err != nil {
+ return Value{}, err
+ }
+ uintVal.ABIType = ufixedValueType
+ return uintVal, nil
+}
+
+// MakeString takes a string and returns an ABI String type Value.
+func MakeString(value string) Value {
+ return Value{
+ ABIType: MakeStringType(),
+ value: value,
+ }
+}
+
+// MakeByte takes a byte and returns an ABI Byte type value.
+func MakeByte(value byte) Value {
+ return Value{
+ ABIType: MakeByteType(),
+ value: value,
+ }
+}
+
+// MakeAddress takes an [32]byte array and returns an ABI Address type value.
+func MakeAddress(value [32]byte) Value {
+ return Value{
+ ABIType: MakeAddressType(),
+ value: value,
+ }
+}
+
+// MakeDynamicArray takes an array of ABI value (can be empty) and element type,
+// returns an ABI dynamic length array value.
+func MakeDynamicArray(values []Value, elemType Type) (Value, error) {
+ if len(values) >= math.MaxUint16 {
+ return Value{}, fmt.Errorf("dynamic array make error: pass in array length larger than maximum of uint16")
+ }
+ for i := 0; i < len(values); i++ {
+ if !values[i].ABIType.Equal(elemType) {
+ return Value{}, fmt.Errorf("type mismatch: %s and %s",
+ values[i].ABIType.String(), elemType.String())
+ }
+ }
+ return Value{
+ ABIType: MakeDynamicArrayType(elemType),
+ value: values,
+ }, nil
+}
+
+// MakeStaticArray takes an array of ABI value and returns an ABI static length array value.
+func MakeStaticArray(values []Value) (Value, error) {
+ if len(values) >= math.MaxUint16 {
+ return Value{}, fmt.Errorf("static array make error: pass in array length larger than maximum of uint16")
+ } else if len(values) == 0 {
+ return Value{}, fmt.Errorf("static array make error: 0 array element passed in")
+ }
+ for i := 0; i < len(values); i++ {
+ if !values[i].ABIType.Equal(values[0].ABIType) {
+ return Value{}, fmt.Errorf("type mismatch: %s and %s",
+ values[i].ABIType.String(), values[0].ABIType.String())
+ }
+ }
+ return Value{
+ ABIType: MakeStaticArrayType(values[0].ABIType, uint16(len(values))),
+ value: values,
+ }, nil
+}
+
+// MakeTuple takes an array of ABI values and returns an ABI tuple value.
+func MakeTuple(values []Value) (Value, error) {
+ if len(values) >= math.MaxUint16 {
+ return Value{}, fmt.Errorf("tuple make error: pass in tuple length larger than maximum of uint16")
+ }
+ tupleType := make([]Type, len(values))
+ for i := 0; i < len(values); i++ {
+ tupleType[i] = values[i].ABIType
+ }
+
+ castedTupleType, err := MakeTupleType(tupleType)
+ if err != nil {
+ return Value{}, err
+ }
+
+ return Value{
+ ABIType: castedTupleType,
+ value: values,
+ }, nil
+}
+
+// MakeBool takes a boolean value and returns an ABI bool value.
+func MakeBool(value bool) Value {
+ return Value{
+ ABIType: MakeBoolType(),
+ value: value,
+ }
+}
+
+func checkUintValid(t Type, bitSize uint16) bool {
+ return t.abiTypeID == Uint && t.bitSize <= bitSize
+}
+
+// GetUint8 tries to retreve an uint8 from an ABI Value.
+func (v Value) GetUint8() (uint8, error) {
+ if !checkUintValid(v.ABIType, 8) {
+ return 0, fmt.Errorf("value type mismatch or bitSize too large")
+ }
+ bigIntForm, err := v.GetUint()
+ if err != nil {
+ return 0, err
+ }
+ return uint8(bigIntForm.Uint64()), nil
+}
+
+// GetUint16 tries to retrieve an uint16 from an ABI Value.
+func (v Value) GetUint16() (uint16, error) {
+ if !checkUintValid(v.ABIType, 16) {
+ return 0, fmt.Errorf("value type mismatch or bitSize too large")
+ }
+ bigIntForm, err := v.GetUint()
+ if err != nil {
+ return 0, err
+ }
+ return uint16(bigIntForm.Uint64()), nil
+}
+
+// GetUint32 tries to retrieve an uint32 from an ABI Value.
+func (v Value) GetUint32() (uint32, error) {
+ if !checkUintValid(v.ABIType, 32) {
+ return 0, fmt.Errorf("value type mismatch or bitSize too large")
+ }
+ bigIntForm, err := v.GetUint()
+ if err != nil {
+ return 0, err
+ }
+ return uint32(bigIntForm.Uint64()), nil
+}
+
+// GetUint64 tries to retrieve an uint64 from an ABI Value.
+func (v Value) GetUint64() (uint64, error) {
+ if !checkUintValid(v.ABIType, 64) {
+ return 0, fmt.Errorf("value type mismatch or bitSize too large")
+ }
+ bigIntForm, err := v.GetUint()
+ if err != nil {
+ return 0, err
+ }
+ return bigIntForm.Uint64(), nil
+}
+
+// GetUint tries to retrieve an big uint from an ABI Value.
+func (v Value) GetUint() (*big.Int, error) {
+ if v.ABIType.abiTypeID != Uint {
+ return nil, fmt.Errorf("value type mismatch")
+ }
+ bigIntForm := v.value.(*big.Int)
+ sizeThreshold := new(big.Int).Lsh(big.NewInt(1), uint(v.ABIType.bitSize))
+ if sizeThreshold.Cmp(bigIntForm) <= 0 {
+ return nil, fmt.Errorf("value exceeds uint bitSize scope")
+ }
+ return bigIntForm, nil
+}
+
+// GetUfixed tries to retrieve an big integer number from an ABI Value.
+func (v Value) GetUfixed() (*big.Int, error) {
+ if v.ABIType.abiTypeID != Ufixed {
+ return nil, fmt.Errorf("value type mismatch, should be ufixed")
+ }
+ bigIntForm := v.value.(*big.Int)
+ sizeThreshold := new(big.Int).Lsh(big.NewInt(1), uint(v.ABIType.bitSize))
+ if sizeThreshold.Cmp(bigIntForm) <= 0 {
+ return nil, fmt.Errorf("value exceeds ufixed bitSize scope")
+ }
+ return bigIntForm, nil
+}
+
+// GetString tries to retrieve a string from ABI Value.
+func (v Value) GetString() (string, error) {
+ if v.ABIType.abiTypeID != String {
+ return "", fmt.Errorf("value type mismatch, should be ufixed")
+ }
+ stringForm := v.value.(string)
+ return stringForm, nil
+}
+
+// GetByte tries to retrieve a byte from ABI Value.
+func (v Value) GetByte() (byte, error) {
+ if v.ABIType.abiTypeID != Byte {
+ return byte(0), fmt.Errorf("value type mismatch, should be bytes")
+ }
+ bytesForm := v.value.(byte)
+ return bytesForm, nil
+}
+
+// GetAddress tries to retrieve a [32]byte array from ABI Value.
+func (v Value) GetAddress() ([32]byte, error) {
+ if v.ABIType.abiTypeID != Address {
+ return [32]byte{}, fmt.Errorf("value type mismatch, should be address")
+ }
+ addressForm := v.value.([32]byte)
+ return addressForm, nil
+}
+
+// GetValueByIndex retrieve value element by the index passed in
+func (v Value) GetValueByIndex(index uint16) (Value, error) {
+ switch v.ABIType.abiTypeID {
+ case ArrayDynamic:
+ elements := v.value.([]Value)
+ if len(elements) <= int(index) {
+ return Value{}, fmt.Errorf("cannot get element: index out of scope")
+ }
+ return elements[index], nil
+ case ArrayStatic, Tuple:
+ elements := v.value.([]Value)
+ if v.ABIType.staticLength <= index {
+ return Value{}, fmt.Errorf("cannot get element: index out of scope")
+ }
+ return elements[index], nil
+ default:
+ return Value{}, fmt.Errorf("cannot get value by index for non array-like type")
+ }
+}
+
+// GetBool tries to retrieve a boolean value from the ABI Value.
+func (v Value) GetBool() (bool, error) {
+ if v.ABIType.abiTypeID != Bool {
+ return false, fmt.Errorf("value type mismatch, should be bool")
+ }
+ boolForm := v.value.(bool)
+ return boolForm, nil
+}
diff --git a/go.mod b/go.mod
index f779709580..09c57773d0 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/algorand/oapi-codegen v1.3.5-algorand5
github.com/algorand/websocket v1.4.2
github.com/aws/aws-sdk-go v1.16.5
+ github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e
github.com/cpuguy83/go-md2man v1.0.8 // indirect
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018
github.com/dchest/siphash v1.2.1
diff --git a/go.sum b/go.sum
index 1966c8ede6..854436cf7c 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,8 @@ github.com/algorand/websocket v1.4.2 h1:zMB7ukz+c7tcef8rVqmKQTv6KQtxXtCFuiAqKaE7
github.com/algorand/websocket v1.4.2/go.mod h1:0nFSn+xppw/GZS9hgWPS3b8/4FcA3Pj7XQxm+wqHGx8=
github.com/aws/aws-sdk-go v1.16.5 h1:NVxzZXIuwX828VcJrpNxxWjur1tlOBISdMdDdHIKHcc=
github.com/aws/aws-sdk-go v1.16.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas=
+github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s=
github.com/cpuguy83/go-md2man v1.0.8 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M=
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
github.com/cyberdelia/templates v0.0.0-20191230040416-20a325f050d4 h1:Fphwr1XDjkTR/KFbrrkLfY6D2CEOlHqFGomQQrxcHFs=