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=