From 42a56b4559307f0027cd1ca7d5e0ff0232b971b3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 Aug 2021 14:42:10 -0400 Subject: [PATCH 01/29] add abi encoding --- cmd/goal/abi/abi.go | 1106 +++++++++++++++++++++++++++++++ cmd/goal/abi/abi_encode_test.go | 485 ++++++++++++++ cmd/goal/abi/abi_type_test.go | 532 +++++++++++++++ 3 files changed, 2123 insertions(+) create mode 100644 cmd/goal/abi/abi.go create mode 100644 cmd/goal/abi/abi_encode_test.go create mode 100644 cmd/goal/abi/abi_type_test.go diff --git a/cmd/goal/abi/abi.go b/cmd/goal/abi/abi.go new file mode 100644 index 0000000000..c0a1470b4c --- /dev/null +++ b/cmd/goal/abi/abi.go @@ -0,0 +1,1106 @@ +package abi + +import ( + "encoding/binary" + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + "unicode" +) + +/* + 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) +*/ + +type BaseType uint32 + +const ( + Uint BaseType = iota + Byte + Ufixed + Bool + ArrayStatic + Address + ArrayDynamic + String + Tuple +) + +type Type struct { + typeFromEnum BaseType + childTypes []Type + + // only can be applied to `uint` size or `ufixed` size + typeSize uint16 + // only can be applied to `ufixed` precision + typePrecision 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 + */ + // TODO may want to change back to uint32/uint64 + staticLength uint16 +} + +// String serialization +func (t Type) String() string { + switch t.typeFromEnum { + case Uint: + return "uint" + strconv.Itoa(int(t.typeSize)) + case Byte: + return "byte" + case Ufixed: + return "ufixed" + strconv.Itoa(int(t.typeSize)) + "x" + strconv.Itoa(int(t.typePrecision)) + 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: + return "Bruh you should not be here" + } +} + +// TypeFromString de-serialization +func TypeFromString(str string) (Type, error) { + switch { + case strings.HasSuffix(str, "[]"): + arrayArgType, err := TypeFromString(str[:len(str)-2]) + if err != nil { + return arrayArgType, err + } + return MakeDynamicArrayType(arrayArgType), nil + case strings.HasSuffix(str, "]") && len(str) >= 2 && unicode.IsDigit(rune(str[len(str)-2])): + stringMatches := regexp.MustCompile(`^[a-z\d\[\](),]+\[([1-9][\d]*)]$`).FindStringSubmatch(str) + // match the string itself, then array length + if len(stringMatches) != 2 { + return Type{}, fmt.Errorf("static array ill formated: %s", str) + } + // guaranteed that the length of array is existing + arrayLengthStr := stringMatches[1] + arrayLength, err := strconv.ParseUint(arrayLengthStr, 10, 32) + if err != nil { + return Type{}, err + } + // parse the array element type + arrayType, err := TypeFromString(str[:len(str)-(2+len(arrayLengthStr))]) + 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) + } + uintTypeRes, err := MakeUintType(uint16(typeSize)) + if err != nil { + return Type{}, err + } + return uintTypeRes, nil + case str == "byte": + return MakeByteType(), nil + case strings.HasPrefix(str, "ufixed"): + stringMatches := regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`).FindStringSubmatch(str) + // match string itself, then type-size, 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 + } + ufixedTypeRes, err := MakeUFixedType(uint16(ufixedSize), uint16(ufixedPrecision)) + if err != nil { + return Type{}, err + } + return ufixedTypeRes, nil + 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] == ')': + if strings.Contains(str[1:len(str)-1], " ") { + return Type{}, fmt.Errorf("tuple should not contain space") + } + 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), nil + default: + return Type{}, fmt.Errorf("cannot convert a string %s to an ABI type", str) + } +} + +type segmentIndex struct{ left, right int } + +func parseTupleContent(str string) ([]string, error) { + // argument str is the content between parentheses of tuple, i.e. + // (...... str ......) + // ^ ^ + parenSegmentRecord, stack := make([]segmentIndex, 0), make([]int, 0) + // get the most exterior parentheses segment (not overlapped by other parentheses) + // illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"] + 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, segmentIndex{ + left: leftParenIndex, + right: index, + }) + } + } + } + if len(stack) != 0 { + return []string{}, fmt.Errorf("unpaired parentheses: %s", str) + } + + // kudos to Jason Paulos + if strings.Contains(str, ",,") { + return []string{}, fmt.Errorf("no consequtive commas") + } + + // take out tuple-formed type str in tuple argument + strCopied := str + for i := len(parenSegmentRecord) - 1; i >= 0; i-- { + segment := parenSegmentRecord[i] + strCopied = strCopied[:segment.left] + strCopied[segment.right+1:] + } + + // maintain list of empty strings as placeholders for tuple-formed type str + segments := strings.Split(strCopied, ",") + emptyStrIndex := make([]int, 0) + for index, str := range segments { + if str == "" { + emptyStrIndex = append(emptyStrIndex, index) + } + } + + if len(emptyStrIndex) != len(parenSegmentRecord) { + return []string{}, fmt.Errorf("head tail comma is not allowed") + } + + // replace back the tuple-formed type str + for index, replaceIndex := range emptyStrIndex { + segments[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] + } + + return segments, nil +} + +func MakeUintType(typeSize uint16) (Type, error) { + if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { + return Type{}, fmt.Errorf("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) + } + return Type{ + typeFromEnum: Uint, + typeSize: typeSize, + }, nil +} + +func MakeByteType() Type { + return Type{ + typeFromEnum: Byte, + } +} + +func MakeUFixedType(typeSize uint16, typePrecision uint16) (Type, error) { + if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { + return Type{}, fmt.Errorf("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) + } + if typePrecision > 160 || typePrecision < 1 { + return Type{}, fmt.Errorf("type uint precision range [1, 160]") + } + return Type{ + typeFromEnum: Ufixed, + typeSize: typeSize, + typePrecision: typePrecision, + }, nil +} + +func MakeBoolType() Type { + return Type{ + typeFromEnum: Bool, + } +} + +func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { + return Type{ + typeFromEnum: ArrayStatic, + childTypes: []Type{argumentType}, + staticLength: arrayLength, + } +} + +func MakeAddressType() Type { + return Type{ + typeFromEnum: Address, + } +} + +func MakeDynamicArrayType(argumentType Type) Type { + return Type{ + typeFromEnum: ArrayDynamic, + childTypes: []Type{argumentType}, + } +} + +func MakeStringType() Type { + return Type{ + typeFromEnum: String, + } +} + +func MakeTupleType(argumentTypes []Type) Type { + return Type{ + typeFromEnum: Tuple, + childTypes: argumentTypes, + staticLength: uint16(len(argumentTypes)), + } +} + +func (t Type) Equal(t0 Type) bool { + // assume t and t0 are well-formed + switch t.typeFromEnum { + case Uint: + return t.typeFromEnum == t0.typeFromEnum && t.typeSize == t0.typeSize + case Ufixed: + if t0.typeFromEnum != Ufixed { + return false + } else if t0.typePrecision != t.typePrecision || t0.typeSize != t.typeSize { + return false + } else { + return true + } + case ArrayStatic: + if t0.typeFromEnum != ArrayStatic { + return false + } else if len(t.childTypes) != len(t0.childTypes) || len(t0.childTypes) != 1 { + return false + } else if t.staticLength != t0.staticLength { + return false + } else { + return t.childTypes[0].Equal(t0.childTypes[0]) + } + case ArrayDynamic: + if t0.typeFromEnum != ArrayDynamic { + return false + } else if len(t.childTypes) != len(t0.childTypes) || len(t0.childTypes) != 1 { + return false + } else { + return t.childTypes[0].Equal(t0.childTypes[0]) + } + case Tuple: + if t0.typeFromEnum != Tuple { + return false + } else if t.staticLength != t0.staticLength || int(t.staticLength) != len(t0.childTypes) { + return false + } else { + for i := 0; i < int(t.staticLength); i++ { + compRes := t.childTypes[i].Equal(t0.childTypes[i]) + if !compRes { + return false + } + } + return true + } + default: + return t.typeFromEnum == t0.typeFromEnum + } +} + +func (t Type) IsDynamic() bool { + switch t.typeFromEnum { + case ArrayStatic: + return t.childTypes[0].IsDynamic() + case ArrayDynamic, String: + return true + case Tuple: + for _, childT := range t.childTypes { + if childT.IsDynamic() { + return true + } + } + return false + default: + return false + } +} + +func (t Type) ByteLen() (int, error) { + if t.IsDynamic() { + return -1, fmt.Errorf("dynamic type") + } + + switch t.typeFromEnum { + case Address: + return 32, nil + case Byte: + return 1, nil + case Uint, Ufixed: + return int(t.typeSize / 8), nil + case Bool: + return 1, nil + case ArrayStatic: + elemByteLen, err := t.childTypes[0].ByteLen() + if err != nil { + return -1, err + } + return int(t.staticLength) * elemByteLen, nil + case Tuple: + size := 0 + for _, childT := range t.childTypes { + childByteSize, err := childT.ByteLen() + if err != nil { + return -1, err + } + size += childByteSize + } + return size, nil + default: + return -1, fmt.Errorf("bruh you should not be here") + } +} + +type Value struct { + valueType Type + value interface{} +} + +func (v Value) arrayToTuple() (Value, error) { + var childT []Type + var valueArr []Value + + switch v.valueType.typeFromEnum { + case String: + strValue, err := GetString(v) + if err != nil { + return Value{}, err + } + strByte := []byte(strValue) + childT, valueArr = make([]Type, len(strByte)), make([]Value, len(strByte)) + for i := 0; i < len(strByte); i++ { + childT[i] = MakeByteType() + valueArr[i] = MakeByte(strByte[i]) + } + case Address: + addr, err := GetAddress(v) + if err != nil { + return Value{}, err + } + childT, valueArr = make([]Type, 32), make([]Value, 32) + for i := 0; i < 32; i++ { + childT[i] = MakeByteType() + valueArr[i] = MakeByte(addr[i]) + } + case ArrayStatic: + childT = make([]Type, v.valueType.staticLength) + for i := 0; i < int(v.valueType.staticLength); i++ { + childT[i] = v.valueType.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.valueType.childTypes[0] + } + valueArr = arrayElems + default: + return Value{}, fmt.Errorf("value type not supported to convertion to tuple") + } + + return Value{ + valueType: MakeTupleType(childT), + value: valueArr, + }, nil +} + +// Encode serialization +func (v Value) Encode() ([]byte, error) { + switch v.valueType.typeFromEnum { + case Uint: + bigIntValue, err := GetUint(v) + if err != nil { + return []byte{}, err + } + bigIntBytes := bigIntValue.Bytes() + buffer := make([]byte, v.valueType.typeSize/8-uint16(len(bigIntBytes))) + buffer = append(buffer, bigIntBytes...) + return buffer, nil + case Ufixed: + ufixedValue, err := GetUfixed(v) + if err != nil { + return []byte{}, err + } + denomSize := big.NewInt(1).Exp(big.NewInt(10), big.NewInt(int64(v.valueType.typePrecision)), nil) + denomRat := big.NewRat(1, 1).SetFrac(denomSize, big.NewInt(1)) + numRat := denomRat.Mul(denomRat, ufixedValue) + encodeVal := numRat.Num() + encodeBuffer := encodeVal.Bytes() + buffer := make([]byte, v.valueType.typeSize/8-uint16(len(encodeBuffer))) + buffer = append(buffer, encodeBuffer...) + return buffer, nil + case Bool: + boolValue, err := GetBool(v) + if err != nil { + return []byte{}, err + } + if boolValue { + return []byte{0x80}, nil + } else { + return []byte{0x00}, nil + } + case Byte: + bytesValue, err := GetByte(v) + 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.valueType.childTypes) + lengthEncode := make([]byte, 2) + 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("bruh you should not be here in encoding: unknown type error") + } +} + +func findBoolLR(typeList []Type, index int, delta int) int { + until := 0 + for true { + curr := index + delta*until + if typeList[curr].typeFromEnum == Bool { + if curr != len(typeList)-1 && delta > 0 { + until++ + } else if curr > 0 && delta < 0 { + until++ + } else { + break + } + } else { + until-- + break + } + } + return until +} + +func compressMultipleBool(valueList []Value) (uint8, error) { + var res uint8 = 0 + if len(valueList) > 8 { + return 0, fmt.Errorf("value list passed in should be less than length 8") + } + for i := 0; i < len(valueList); i++ { + if valueList[i].valueType.typeFromEnum != Bool { + return 0, fmt.Errorf("bool type not matching in compressMultipleBool") + } + boolVal, err := GetBool(valueList[i]) + if err != nil { + return 0, err + } + if boolVal { + res |= 1 << uint(7-i) + } + } + return res, nil +} + +func tupleEncoding(v Value) ([]byte, error) { + if v.valueType.typeFromEnum != Tuple { + return []byte{}, fmt.Errorf("tupe not supported in tupleEncoding") + } + heads, tails := make([][]byte, len(v.valueType.childTypes)), make([][]byte, len(v.valueType.childTypes)) + isDynamicIndex := make(map[int]bool) + tupleElems := v.value.([]Value) + for i := 0; i < len(v.valueType.childTypes); i++ { + switch tupleElems[i].valueType.IsDynamic() { + case true: + headsPlaceholder := []byte{0x00, 0x00} + heads[i] = headsPlaceholder + isDynamicIndex[i] = true + tailEncoding, err := tupleElems[i].Encode() + if err != nil { + return []byte{}, err + } + tails[i] = tailEncoding + case false: + if tupleElems[i].valueType.typeFromEnum == Bool { + // search previous bool + before := findBoolLR(v.valueType.childTypes, i, -1) + // search after bool + after := findBoolLR(v.valueType.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 + tails[i] = nil + } else { + encodeTi, err := tupleElems[i].Encode() + if err != nil { + return []byte{}, err + } + heads[i] = encodeTi + tails[i] = nil + } + isDynamicIndex[i] = false + } + } + + // adjust heads for dynamic type + headLength := 0 + for _, headTi := range heads { + headLength += len(headTi) + } + + tailCurrLength := 0 + for i := 0; i < len(heads); i++ { + if isDynamicIndex[i] { + headValue := headLength + tailCurrLength + binary.BigEndian.PutUint16(heads[i], uint16(headValue)) + } + tailCurrLength += len(tails[i]) + } + + head, tail := make([]byte, 0), make([]byte, 0) + for i := 0; i < len(v.valueType.childTypes); i++ { + head = append(head, heads[i]...) + tail = append(tail, tails[i]...) + } + return append(head, tail...), nil +} + +// Decode de-serialization +func Decode(valueByte []byte, valueType Type) (Value, error) { + switch valueType.typeFromEnum { + case Uint: + if len(valueByte) != int(valueType.typeSize)/8 { + return Value{}, + fmt.Errorf("uint size %d byte, given byte size unmatch", int(valueType.typeSize)/8) + } + uintValue := big.NewInt(0).SetBytes(valueByte) + return MakeUint(uintValue, valueType.typeSize) + case Ufixed: + if len(valueByte) != int(valueType.typeSize)/8 { + return Value{}, + fmt.Errorf("ufixed size %d byte, given byte size unmatch", int(valueType.typeSize)/8) + } + ufixedNumerator := big.NewInt(0).SetBytes(valueByte) + ufixedDenominator := big.NewInt(0).Exp( + big.NewInt(10), big.NewInt(int64(valueType.typePrecision)), + nil, + ) + ufixedValue := big.NewRat(1, 1).SetFrac(ufixedNumerator, ufixedDenominator) + return MakeUfixed(ufixedValue, valueType.typeSize, valueType.typePrecision) + case Bool: + if len(valueByte) != 1 { + return Value{}, fmt.Errorf("boolean byte should be length 1 byte") + } + boolValue := valueByte[0] > 0 + 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 := MakeTupleType(childT) + tupleDecoded, err := tupleDecoding(valueByte, converted) + if err != nil { + return Value{}, err + } + tupleDecoded.valueType = valueType + return tupleDecoded, nil + case Address: + if len(valueByte) != 32 { + return Value{}, fmt.Errorf("address should be length 32") + } + var byteAssign [32]byte + copy(byteAssign[:], valueByte) + return MakeAddress(byteAssign), nil + case ArrayDynamic: + if len(valueByte) < 2 { + return Value{}, fmt.Errorf("dynamic array format corrupted") + } + dynamicLen := binary.BigEndian.Uint16(valueByte[:2]) + childT := make([]Type, dynamicLen) + for i := 0; i < int(dynamicLen); i++ { + childT[i] = valueType.childTypes[0] + } + converted := MakeTupleType(childT) + tupleDecoded, err := tupleDecoding(valueByte[2:], converted) + if err != nil { + return Value{}, err + } + tupleDecoded.valueType = valueType + return tupleDecoded, nil + case String: + if len(valueByte) < 2 { + return Value{}, fmt.Errorf("string format corrupted") + } + stringLenBytes := valueByte[:2] + byteLen := binary.BigEndian.Uint16(stringLenBytes) + if len(valueByte[2:]) != int(byteLen) { + return Value{}, fmt.Errorf("string representation in byte: length not matching") + } + return MakeString(string(valueByte[2:])), nil + case Tuple: + return tupleDecoding(valueByte, valueType) + default: + return Value{}, fmt.Errorf("bruh you should not be here in decoding: unknown type error") + } +} + +func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { + dynamicSegments, valuePartition := make([]segmentIndex, 0), make([][]byte, 0) + iterIndex := 0 + for i := 0; i < len(valueType.childTypes); i++ { + if valueType.childTypes[i].IsDynamic() { + if len(valueBytes[iterIndex:]) < 2 { + return Value{}, fmt.Errorf("ill formed tuple encoding") + } + dynamicIndex := binary.BigEndian.Uint16(valueBytes[iterIndex : iterIndex+2]) + if len(dynamicSegments) > 0 { + dynamicSegments[len(dynamicSegments)-1].right = int(dynamicIndex) - 1 + } + dynamicSegments = append(dynamicSegments, segmentIndex{ + left: int(dynamicIndex), + right: -1, + }) + valuePartition = append(valuePartition, nil) + iterIndex += 2 + } else { + // if bool ... + if valueType.childTypes[i].typeFromEnum == 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++ { + // each time check the significant bit, from left to right + boolValue := valueBytes[iterIndex] << boolIndex + if boolValue >= 0x80 { + valuePartition = append(valuePartition, []byte{0x80}) + } else { + valuePartition = append(valuePartition, []byte{0x00}) + } + } + i += after + iterIndex += 1 + } + } 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) - 1 + iterIndex = len(valueBytes) + } + if iterIndex < len(valueBytes) { + return Value{}, fmt.Errorf("input byte not fully consumed") + } + + // check segment indices are valid + segIndexArr := make([]int, len(dynamicSegments)*2) + for index, segment := range dynamicSegments { + segIndexArr[index*2] = segment.left + segIndexArr[index*2+1] = segment.right + } + for i := 0; i < len(segIndexArr); i++ { + if i%2 == 1 { + if i != len(segIndexArr)-1 && segIndexArr[i]+1 != segIndexArr[i+1] { + return Value{}, fmt.Errorf("dynamic segment should sit next to each other") + } + } else { + if segIndexArr[i] >= segIndexArr[i+1] { + return Value{}, fmt.Errorf("dynamic segment should display a [l, r] space") + } + } + } + + segIndex := 0 + for i := 0; i < len(valueType.childTypes); i++ { + if valuePartition[i] == nil { + valuePartition[i] = valueBytes[dynamicSegments[segIndex].left : dynamicSegments[segIndex].right+1] + segIndex++ + } + } + + 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{ + valueType: valueType, + value: values, + }, nil +} + +func MakeUint8(value uint8) (Value, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 8) +} + +func MakeUint16(value uint16) (Value, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 16) +} + +func MakeUint32(value uint32) (Value, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 32) +} + +func MakeUint64(value uint64) (Value, error) { + bigInt := big.NewInt(int64(0)).SetUint64(value) + return MakeUint(bigInt, 64) +} + +func MakeUint(value *big.Int, size uint16) (Value, error) { + typeUint, err := MakeUintType(size) + if err != nil { + return Value{}, err + } + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + if value.Cmp(upperLimit) >= 0 { + return Value{}, fmt.Errorf("passed value larger than uint size %d", size) + } + return Value{ + valueType: typeUint, + value: value, + }, nil +} + +func MakeUfixed(value *big.Rat, size uint16, precision uint16) (Value, error) { + ufixedValueType, err := MakeUFixedType(size, precision) + if err != nil { + return Value{}, nil + } + denomSize := big.NewInt(0).Exp( + big.NewInt(10), big.NewInt(int64(precision)), + nil, + ) + numUpperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + ufixedLimit := big.NewRat(1, 1).SetFrac(numUpperLimit, denomSize) + if value.Denom().Cmp(denomSize) > 0 { + return Value{}, fmt.Errorf("value precision overflow") + } + if value.Cmp(big.NewRat(0, 1)) < 0 || value.Cmp(ufixedLimit) >= 0 { + return Value{}, fmt.Errorf("ufixed value out of scope") + } + return Value{ + valueType: ufixedValueType, + value: value, + }, nil +} + +func MakeString(value string) Value { + return Value{ + valueType: MakeStringType(), + value: value, + } +} + +func MakeByte(value byte) Value { + return Value{ + valueType: MakeByteType(), + value: value, + } +} + +func MakeAddress(value [32]byte) Value { + return Value{ + valueType: MakeAddressType(), + value: value, + } +} + +func MakeDynamicArray(values []Value, elemType Type) (Value, error) { + for i := 0; i < len(values); i++ { + if !values[i].valueType.Equal(elemType) { + return Value{}, fmt.Errorf("type mismatch: %s and %s", + values[i].valueType.String(), elemType.String()) + } + } + return Value{ + valueType: MakeDynamicArrayType(elemType), + value: values, + }, nil +} + +func MakeStaticArray(values []Value, elemType Type) (Value, error) { + for i := 0; i < len(values); i++ { + if !values[i].valueType.Equal(elemType) { + return Value{}, fmt.Errorf("type mismatch: %s and %s", + values[i].valueType.String(), elemType.String()) + } + } + return Value{ + valueType: MakeStaticArrayType(elemType, uint16(len(values))), + value: values, + }, nil +} + +func MakeTuple(values []Value, tupleType []Type) (Value, error) { + if len(values) != len(tupleType) { + return Value{}, fmt.Errorf("tuple make: tuple element number unmatch with tuple type number") + } + if len(values) == 0 { + return Value{}, fmt.Errorf("empty tuple") + } + for i := 0; i < len(values); i++ { + if !values[i].valueType.Equal(tupleType[i]) { + return Value{}, fmt.Errorf("type mismatch: %s and %s", + values[i].valueType.String(), tupleType[i].String()) + } + } + return Value{ + valueType: MakeTupleType(tupleType), + value: values, + }, nil +} + +func MakeBool(value bool) Value { + return Value{ + valueType: MakeBoolType(), + value: value, + } +} + +func GetUint8(value Value) (uint8, error) { + if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 8 { + return 0, fmt.Errorf("value type unmatch or size too large") + } + bigIntForm, err := GetUint(value) + if err != nil { + return 0, err + } + return uint8(bigIntForm.Uint64()), nil +} + +func GetUint16(value Value) (uint16, error) { + if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 16 { + return 0, fmt.Errorf("value type unmatch or size too large") + } + bigIntForm, err := GetUint(value) + if err != nil { + return 0, err + } + return uint16(bigIntForm.Uint64()), nil +} + +func GetUint32(value Value) (uint32, error) { + if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 32 { + return 0, fmt.Errorf("value type unmatch or size too large") + } + bigIntForm, err := GetUint(value) + if err != nil { + return 0, err + } + return uint32(bigIntForm.Uint64()), nil +} + +func GetUint64(value Value) (uint64, error) { + if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 64 { + return 0, fmt.Errorf("value type unmatch or size too large") + } + bigIntForm, err := GetUint(value) + if err != nil { + return 0, err + } + return bigIntForm.Uint64(), nil +} + +func GetUint(value Value) (*big.Int, error) { + if value.valueType.typeFromEnum != Uint { + return nil, fmt.Errorf("value type unmatch") + } + bigIntForm := value.value.(*big.Int) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(value.valueType.typeSize)) + if sizeThreshold.Cmp(bigIntForm) <= 0 { + return nil, fmt.Errorf("value is larger than uint size") + } + return bigIntForm, nil +} + +func GetUfixed(value Value) (*big.Rat, error) { + if value.valueType.typeFromEnum != Ufixed { + return nil, fmt.Errorf("value type unmatch, should be ufixed") + } + ufixedForm := value.value.(*big.Rat) + numinatorSize := big.NewInt(0).Lsh(big.NewInt(1), uint(value.valueType.typeSize)) + denomSize := big.NewInt(0).Exp( + big.NewInt(10), big.NewInt(int64(value.valueType.typePrecision)), + nil, + ) + ufixedLimit := big.NewRat(1, 1).SetFrac(numinatorSize, denomSize) + if ufixedForm.Denom().Cmp(denomSize) > 0 { + return nil, fmt.Errorf("denominator size overflow") + } + if ufixedForm.Cmp(big.NewRat(0, 1)) < 0 || ufixedForm.Cmp(ufixedLimit) >= 0 { + return nil, fmt.Errorf("ufixed < 0 or ufixed larger than limit") + } + return ufixedForm, nil +} + +func GetString(value Value) (string, error) { + if value.valueType.typeFromEnum != String { + return "", fmt.Errorf("value type unmatch, should be ufixed") + } + stringForm := value.value.(string) + return stringForm, nil +} + +func GetByte(value Value) (byte, error) { + if value.valueType.typeFromEnum != Byte { + return byte(0), fmt.Errorf("value type unmatch, should be bytes") + } + bytesForm := value.value.(byte) + return bytesForm, nil +} + +func GetAddress(value Value) ([32]byte, error) { + if value.valueType.typeFromEnum != Address { + return [32]byte{}, fmt.Errorf("value type unmatch, should be address") + } + addressForm := value.value.([32]byte) + return addressForm, nil +} + +func GetDynamicArrayByIndex(value Value, index uint16) (Value, error) { + if value.valueType.typeFromEnum != ArrayDynamic { + return Value{}, fmt.Errorf("value type unmatch, should be dynamic array") + } + elements := value.value.([]Value) + if int(index) >= len(elements) { + return Value{}, fmt.Errorf("dynamic array cannot get element: index out of scope") + } + return elements[index], nil +} + +func GetStaticArrayByIndex(value Value, index uint16) (Value, error) { + if value.valueType.typeFromEnum != ArrayStatic { + return Value{}, fmt.Errorf("value type unmatch, should be static array") + } + if index >= value.valueType.staticLength { + return Value{}, fmt.Errorf("static array cannot get element: index out of scope") + } + elements := value.value.([]Value) + return elements[index], nil +} + +func GetTupleByIndex(value Value, index uint16) (Value, error) { + if value.valueType.typeFromEnum != Tuple { + return Value{}, fmt.Errorf("value type unmatch, should be tuple") + } + elements := value.value.([]Value) + if int(index) >= len(elements) { + return Value{}, fmt.Errorf("tuple cannot get element: index out of scope") + } + return elements[index], nil +} + +func GetBool(value Value) (bool, error) { + if value.valueType.typeFromEnum != Bool { + return false, fmt.Errorf("value type unmatch, should be bool") + } + boolForm := value.value.(bool) + return boolForm, nil +} diff --git a/cmd/goal/abi/abi_encode_test.go b/cmd/goal/abi/abi_encode_test.go new file mode 100644 index 0000000000..3f54687f12 --- /dev/null +++ b/cmd/goal/abi/abi_encode_test.go @@ -0,0 +1,485 @@ +package abi + +import ( + "crypto/rand" + "encoding/binary" + "math/big" + "testing" + + "github.com/chrismcguire/gobberish" + "github.com/stretchr/testify/require" +) + +func TestEncodeValid(t *testing.T) { + 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") + valueUint, err := MakeUint(randomInt, uint16(intSize)) + require.NoError(t, err, "makeUint Fail") + encodedUint, err := valueUint.Encode() + require.NoError(t, err, "uint encode fail") + randomIntByte := randomInt.Bytes() + buffer := make([]byte, intSize/8-len(randomIntByte)) + buffer = append(buffer, randomIntByte...) + require.Equal(t, buffer, encodedUint, "encode uint not match with expected") + } + // 2^[size] - 1 test + 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") + } + + 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++ { + denomLimit := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(precision)), nil) + for i := 0; i < 10; i++ { + randomInt, err := rand.Int(rand.Reader, upperLimit) + require.NoError(t, err, "cryptographic random int init fail") + + ufixedRational := big.NewRat(1, 1).SetFrac(randomInt, denomLimit) + valueUfixed, err := MakeUfixed(ufixedRational, 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^[size] - 1) / (10^[precision]) test + ufixedLargest := big.NewRat(1, 1).SetFrac(largest, denomLimit) + ufixedLargestValue, err := MakeUfixed(ufixedLargest, 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") + } + } + + 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") + require.Equal(t, address, addrEncode, "encode addr not match with expected") + } + + 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") + } + + 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") + } + + for length := 1; length <= 10; length++ { + for i := 0; i < 10; i++ { + utf8Str := gobberish.GenerateString(length) + strValue := MakeString(utf8Str) + utf8ByteLen := len([]byte(utf8Str)) + head := make([]byte, 2) + binary.BigEndian.PutUint16(head, uint16(utf8ByteLen)) + expected := append(head, []byte(utf8Str)...) + strEncode, err := strValue.Encode() + require.NoError(t, err, "string encode fail") + require.Equal(t, expected, strEncode, "encode string not match with expected") + } + } + + 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, MakeBoolType()) + 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") + }) + + 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, MakeBoolType()) + 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") + }) + + 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") + }) + + t.Run("dynamic tuple encoding", func(t *testing.T) { + inputBase := []interface{}{ + "ABC", true, false, true, false, "DEF", + } + tupleElems := make([]Value, len(inputBase)) + 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, []Type{ + MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), + }) + 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") + }) +} + +func TestDecodeValid(t *testing.T) { + 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") + valueUint, err := MakeUint(randomInt, uint16(intSize)) + require.NoError(t, err, "makeUint Fail") + encodedUint, err := valueUint.Encode() + require.NoError(t, err, "uint encode fail") + uintType, err := MakeUintType(uint16(intSize)) + require.NoError(t, err, "uint type make fail") + decodedUint, err := Decode(encodedUint, uintType) + require.NoError(t, err, "decoding uint should not return error") + require.Equal(t, valueUint, decodedUint, "decode uint fail to match expected value") + } + } + + for size := 8; size <= 512; size += 8 { + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + for precision := 1; precision <= 160; precision++ { + denomLimit := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(precision)), nil) + for i := 0; i < 10; i++ { + randomInt, err := rand.Int(rand.Reader, upperLimit) + require.NoError(t, err, "cryptographic random int init fail") + + ufixedRational := big.NewRat(1, 1).SetFrac(randomInt, denomLimit) + valueUfixed, err := MakeUfixed(ufixedRational, 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") + } + } + } + + 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") + } + + 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") + } + + 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") + } + + for length := 1; length <= 10; 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") + } + } + + 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, MakeBoolType()) + 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") + }) + + 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, MakeBoolType()) + 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") + }) + + 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 { + temp, err := MakeUint64(uintVal) + require.NoError(t, err, "make uint64 should not return error") + arrayElems[index] = temp + } + uintT, err := MakeUintType(64) + require.NoError(t, err, "make uint64 type should not return error") + expected, err := MakeStaticArray(arrayElems, uintT) + 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") + }) + + t.Run("dynamic bool array decode", 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, 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") + }) + + 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, []Type{ + MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), + }) + require.NoError(t, err, "make expected value should not return error") + actual, err := Decode(inputEncode, MakeTupleType([]Type{ + MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), + })) + require.NoError(t, err, "decoding dynamic tuple should not return error") + require.Equal(t, expected, actual, "dynamic tuple not match with expected") + }) +} + +func TestDecodeInvalid(t *testing.T) { + 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") + }) + + 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") + }) + + 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") + }) + + 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") + }) + + 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") + }) + + 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") + }) + + 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, MakeTupleType([]Type{ + MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), + })) + require.Error(t, err, "corrupted decoding dynamic tuple should return error") + }) +} diff --git a/cmd/goal/abi/abi_type_test.go b/cmd/goal/abi/abi_type_test.go new file mode 100644 index 0000000000..55998328a0 --- /dev/null +++ b/cmd/goal/abi/abi_type_test.go @@ -0,0 +1,532 @@ +package abi + +import ( + "fmt" + "math/rand" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TODO need a fuzz test for the parsing + +func TestMakeTypeValid(t *testing.T) { + // uint + for i := 8; i <= 512; i += 8 { + uintType, _ := MakeUintType(uint16(i)) + 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, _ := MakeUFixedType(uint16(i), uint16(j)) + 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{ + typeFromEnum: Uint, + typeSize: uint16(32), + }, + ), + testType: "dynamic array", + expected: "uint32[]", + }, + { + input: MakeDynamicArrayType( + MakeDynamicArrayType( + MakeByteType(), + ), + ), + testType: "dynamic array", + expected: "byte[][]", + }, + { + input: MakeStaticArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: uint16(128), + typePrecision: uint16(10), + }, + uint16(100), + ), + testType: "dynamic array", + expected: "ufixed128x10[100]", + }, + { + input: MakeStaticArrayType( + MakeStaticArrayType( + MakeBoolType(), + uint16(128), + ), + uint16(256), + ), + testType: "dynamic array", + expected: "bool[128][256]", + }, + // tuple type + { + input: MakeTupleType( + []Type{ + { + typeFromEnum: Uint, + typeSize: uint16(32), + }, + MakeTupleType( + []Type{ + MakeAddressType(), + MakeByteType(), + MakeStaticArrayType(MakeBoolType(), uint16(10)), + MakeDynamicArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: uint16(256), + typePrecision: uint16(10), + }, + ), + }, + ), + MakeDynamicArrayType(MakeByteType()), + }, + ), + 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) { + // uint + for i := 0; i <= 1000; i++ { + randInput := rand.Uint32() + for randInput%8 == 0 && randInput <= 512 && randInput >= 8 { + randInput = rand.Uint32() + } + // 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 size input %d", randInput) + } + // ufixed + for i := 0; i <= 10000; i++ { + randSize := rand.Uint64() + for randSize%8 == 0 && randSize <= 512 && randSize >= 8 { + randSize = rand.Uint64() + } + randPrecision := rand.Uint32() + for randPrecision >= 1 && randPrecision <= 160 { + randPrecision = rand.Uint32() + } + _, err := MakeUFixedType(uint16(randSize), uint16(randPrecision)) + require.Error(t, err, "MakeUintType: should throw error on size input %d", randSize) + } +} + +func TestTypeFromStringValid(t *testing.T) { + // uint + for i := 8; i <= 512; i += 8 { + expected, _ := MakeUintType(uint16(i)) + actual, err := TypeFromString(expected.String()) + require.Equal(t, nil, 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, _ := MakeUFixedType(uint16(i), uint16(j)) + actual, err := TypeFromString("ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j)) + require.Equal(t, nil, 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{typeFromEnum: Uint, typeSize: 256}), + }, + { + input: "ufixed256x64[]", + testType: "dynamic array", + expected: MakeDynamicArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: 256, + typePrecision: 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{typeFromEnum: Uint, typeSize: uint16(64)}, + ), + uint16(200), + ), + }, + // tuple type + { + input: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", + testType: "tuple type", + expected: MakeTupleType( + []Type{ + { + typeFromEnum: Uint, + typeSize: uint16(32), + }, + MakeTupleType( + []Type{ + MakeAddressType(), + MakeByteType(), + MakeStaticArrayType(MakeBoolType(), uint16(10)), + MakeDynamicArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: uint16(256), + typePrecision: uint16(10), + }, + ), + }, + ), + MakeDynamicArrayType(MakeByteType()), + }, + ), + }, + { + input: "(uint32,(address,byte,bool[10],(ufixed256x10[])))", + testType: "tuple type", + expected: MakeTupleType( + []Type{ + { + typeFromEnum: Uint, + typeSize: uint16(32), + }, + MakeTupleType( + []Type{ + MakeAddressType(), + MakeByteType(), + MakeStaticArrayType(MakeBoolType(), uint16(10)), + MakeTupleType( + []Type{ + MakeDynamicArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: uint16(256), + typePrecision: uint16(10), + }, + ), + }, + ), + }, + ), + }, + ), + }, + { + input: "((uint32),(address,(byte,bool[10],ufixed256x10[])))", + testType: "tuple type", + expected: MakeTupleType( + []Type{ + MakeTupleType( + []Type{ + { + typeFromEnum: Uint, + typeSize: uint16(32), + }, + }, + ), + MakeTupleType( + []Type{ + MakeAddressType(), + MakeTupleType( + []Type{ + MakeByteType(), + MakeStaticArrayType(MakeBoolType(), uint16(10)), + MakeDynamicArrayType( + Type{ + typeFromEnum: Ufixed, + typeSize: uint16(256), + typePrecision: uint16(10), + }, + ), + }, + ), + }, + ), + }, + ), + }, + } + for _, testcase := range testcases { + t.Run(fmt.Sprintf("TypeFromString test %s", testcase.testType), func(t *testing.T) { + actual, err := TypeFromString(testcase.input) + require.Equal(t, nil, 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) { + 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 size 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 size 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 MakeTupleType(resultTypes) +} + +func TestTypeMISC(t *testing.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") + 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 < 1000; 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 < 1000 { + 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 < 1000 { + 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 < 1000 { + 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 { + if testType.typeFromEnum == Tuple { + sizeSum := 0 + for _, childT := range testType.childTypes { + childSize, err := childT.ByteLen() + require.NoError(t, err, "valid tuple child type should not return error: %s", childT.String()) + sizeSum += childSize + } + require.Equal(t, sizeSum, byteLen, + "%s do not match calculated byte length %d", testType.String(), sizeSum) + } else if testType.typeFromEnum == ArrayStatic { + childSize, err := testType.childTypes[0].ByteLen() + require.NoError(t, err, "%s should not return error", 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++ + } +} From ab9104ee8a8638ea0f0515fb684ea962d723b23b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 Aug 2021 16:30:53 -0400 Subject: [PATCH 02/29] accord with go-algorand code format --- cmd/goal/abi/abi.go | 105 +++++++++++++++++++++++++++++--- cmd/goal/abi/abi_encode_test.go | 16 +++++ cmd/goal/abi/abi_type_test.go | 16 +++++ go.mod | 1 + go.sum | 2 + 5 files changed, 131 insertions(+), 9 deletions(-) diff --git a/cmd/goal/abi/abi.go b/cmd/goal/abi/abi.go index c0a1470b4c..53b237de91 100644 --- a/cmd/goal/abi/abi.go +++ b/cmd/goal/abi/abi.go @@ -1,3 +1,19 @@ +// 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 ( @@ -22,20 +38,31 @@ import ( | (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 { typeFromEnum BaseType childTypes []Type @@ -55,7 +82,7 @@ type Type struct { staticLength uint16 } -// String serialization +// String serialize an ABI Type to a string in ABI encoding. func (t Type) String() string { switch t.typeFromEnum { case Uint: @@ -85,7 +112,7 @@ func (t Type) String() string { } } -// TypeFromString de-serialization +// TypeFromString de-serialize ABI type from a string following ABI encoding. func TypeFromString(str string) (Type, error) { switch { case strings.HasSuffix(str, "[]"): @@ -172,8 +199,11 @@ func TypeFromString(str string) (Type, error) { } } +// segmentIndex keeps track of the start and end of a segment in a string. type segmentIndex 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. func parseTupleContent(str string) ([]string, error) { // argument str is the content between parentheses of tuple, i.e. // (...... str ......) @@ -204,7 +234,7 @@ func parseTupleContent(str string) ([]string, error) { // kudos to Jason Paulos if strings.Contains(str, ",,") { - return []string{}, fmt.Errorf("no consequtive commas") + return []string{}, fmt.Errorf("no consecutive commas") } // take out tuple-formed type str in tuple argument @@ -235,6 +265,8 @@ func parseTupleContent(str string) ([]string, error) { return segments, nil } +// MakeUintType makes `Uint` ABI type by taking a type size argument. +// The range of type size is [8, 512] and type size % 8 == 0. func MakeUintType(typeSize uint16) (Type, error) { if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { return Type{}, fmt.Errorf("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) @@ -245,12 +277,16 @@ func MakeUintType(typeSize uint16) (Type, error) { }, nil } +// MakeByteType makes `Byte` ABI type. func MakeByteType() Type { return Type{ typeFromEnum: Byte, } } +// MakeUFixedType makes `UFixed` ABI type by taking type size and type precision as arguments. +// The range of type size is [8, 512] and type size % 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("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) @@ -265,12 +301,15 @@ func MakeUFixedType(typeSize uint16, typePrecision uint16) (Type, error) { }, nil } +// MakeBoolType makes `Bool` ABI type. func MakeBoolType() Type { return Type{ typeFromEnum: 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{ typeFromEnum: ArrayStatic, @@ -279,12 +318,14 @@ func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { } } +// MakeAddressType makes `Address` ABI type. func MakeAddressType() Type { return Type{ typeFromEnum: Address, } } +// MakeDynamicArrayType makes dynamic length array by taking array element type as argument. func MakeDynamicArrayType(argumentType Type) Type { return Type{ typeFromEnum: ArrayDynamic, @@ -292,12 +333,14 @@ func MakeDynamicArrayType(argumentType Type) Type { } } +// MakeStringType makes `String` ABI type. func MakeStringType() Type { return Type{ typeFromEnum: String, } } +// MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument. func MakeTupleType(argumentTypes []Type) Type { return Type{ typeFromEnum: Tuple, @@ -306,6 +349,7 @@ func MakeTupleType(argumentTypes []Type) Type { } } +// Equal method decides the equality of two types: t == t0. func (t Type) Equal(t0 Type) bool { // assume t and t0 are well-formed switch t.typeFromEnum { @@ -356,6 +400,7 @@ func (t Type) Equal(t0 Type) bool { } } +// IsDynamic method decides if an ABI type is dynamic or static. func (t Type) IsDynamic() bool { switch t.typeFromEnum { case ArrayStatic: @@ -374,6 +419,7 @@ func (t Type) IsDynamic() bool { } } +// ByteLen method calculates the byte length of a static ABI type. func (t Type) ByteLen() (int, error) { if t.IsDynamic() { return -1, fmt.Errorf("dynamic type") @@ -409,11 +455,14 @@ func (t Type) ByteLen() (int, error) { } } +// Value struct is the ABI Value, holding ABI Type information and the ABI value representation. type Value struct { valueType Type value interface{} } +// 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 @@ -454,7 +503,7 @@ func (v Value) arrayToTuple() (Value, error) { } valueArr = arrayElems default: - return Value{}, fmt.Errorf("value type not supported to convertion to tuple") + return Value{}, fmt.Errorf("value type not supported to conversion to tuple") } return Value{ @@ -463,7 +512,7 @@ func (v Value) arrayToTuple() (Value, error) { }, nil } -// Encode serialization +// Encode method serialize the ABI value into a byte string of ABI encoding rule. func (v Value) Encode() ([]byte, error) { switch v.valueType.typeFromEnum { case Uint: @@ -495,9 +544,8 @@ func (v Value) Encode() ([]byte, error) { } if boolValue { return []byte{0x80}, nil - } else { - return []byte{0x00}, nil } + return []byte{0x00}, nil case Byte: bytesValue, err := GetByte(v) if err != nil { @@ -531,6 +579,9 @@ func (v Value) Encode() ([]byte, error) { } } +// findBoolLR takes a list of type, the current index, and search direction (+1/-1). +// 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 true { @@ -551,6 +602,7 @@ func findBoolLR(typeList []Type, index int, delta int) int { return until } +// 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 { @@ -571,6 +623,7 @@ func compressMultipleBool(valueList []Value) (uint8, error) { 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.valueType.typeFromEnum != Tuple { return []byte{}, fmt.Errorf("tupe not supported in tupleEncoding") @@ -644,7 +697,8 @@ func tupleEncoding(v Value) ([]byte, error) { return append(head, tail...), nil } -// Decode de-serialization +// 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.typeFromEnum { case Uint: @@ -729,6 +783,8 @@ func Decode(valueByte []byte, valueType Type) (Value, 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, valuePartition := make([]segmentIndex, 0), make([][]byte, 0) iterIndex := 0 @@ -769,7 +825,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } } i += after - iterIndex += 1 + iterIndex++ } } else { // not bool ... @@ -833,26 +889,32 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { }, nil } +// MakeUint8 takes a go `uint8` and gives an ABI Value of ABI type `uint8`. func MakeUint8(value uint8) (Value, error) { bigInt := big.NewInt(int64(value)) return MakeUint(bigInt, 8) } +// MakeUint16 takes a go `uint16` and gives an ABI Value of ABI type `uint16`. func MakeUint16(value uint16) (Value, error) { bigInt := big.NewInt(int64(value)) return MakeUint(bigInt, 16) } +// MakeUint32 takes a go `uint32` and gives an ABI Value of ABI type `uint32`. func MakeUint32(value uint32) (Value, error) { bigInt := big.NewInt(int64(value)) return MakeUint(bigInt, 32) } +// MakeUint64 takes a go `uint64` and gives an ABI Value of ABI type `uint64`. func MakeUint64(value uint64) (Value, error) { bigInt := big.NewInt(int64(0)).SetUint64(value) return MakeUint(bigInt, 64) } +// MakeUint takes a big integer representation and a type size, +// 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 { @@ -868,6 +930,8 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { }, nil } +// MakeUfixed takes a big rational number representation, a type size, and a type precision, +// and returns an ABI Value of ABI UFixedx func MakeUfixed(value *big.Rat, size uint16, precision uint16) (Value, error) { ufixedValueType, err := MakeUFixedType(size, precision) if err != nil { @@ -891,6 +955,7 @@ func MakeUfixed(value *big.Rat, size uint16, precision uint16) (Value, error) { }, nil } +// MakeString takes a string and returns an ABI String type Value. func MakeString(value string) Value { return Value{ valueType: MakeStringType(), @@ -898,6 +963,7 @@ func MakeString(value string) Value { } } +// MakeByte takes a byte and returns an ABI Byte type value. func MakeByte(value byte) Value { return Value{ valueType: MakeByteType(), @@ -905,6 +971,7 @@ func MakeByte(value byte) Value { } } +// MakeAddress takes an [32]byte array and returns an ABI Address type value. func MakeAddress(value [32]byte) Value { return Value{ valueType: MakeAddressType(), @@ -912,6 +979,8 @@ func MakeAddress(value [32]byte) Value { } } +// MakeDynamicArray takes an array of ABI value of elemType, +// and returns an ABI dynamic length array value. func MakeDynamicArray(values []Value, elemType Type) (Value, error) { for i := 0; i < len(values); i++ { if !values[i].valueType.Equal(elemType) { @@ -925,6 +994,8 @@ func MakeDynamicArray(values []Value, elemType Type) (Value, error) { }, nil } +// MakeStaticArray takes an array of ABI value of elemType, +// and returns an ABI static length array value. func MakeStaticArray(values []Value, elemType Type) (Value, error) { for i := 0; i < len(values); i++ { if !values[i].valueType.Equal(elemType) { @@ -938,6 +1009,8 @@ func MakeStaticArray(values []Value, elemType Type) (Value, error) { }, nil } +// MakeTuple takes an array of ABI values and an array of ABI types, +// and returns an ABI tuple value. func MakeTuple(values []Value, tupleType []Type) (Value, error) { if len(values) != len(tupleType) { return Value{}, fmt.Errorf("tuple make: tuple element number unmatch with tuple type number") @@ -957,6 +1030,7 @@ func MakeTuple(values []Value, tupleType []Type) (Value, error) { }, nil } +// MakeBool takes a boolean value and returns an ABI bool value. func MakeBool(value bool) Value { return Value{ valueType: MakeBoolType(), @@ -964,6 +1038,7 @@ func MakeBool(value bool) Value { } } +// GetUint8 tries to retreve an uint8 from an ABI Value. func GetUint8(value Value) (uint8, error) { if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 8 { return 0, fmt.Errorf("value type unmatch or size too large") @@ -975,6 +1050,7 @@ func GetUint8(value Value) (uint8, error) { return uint8(bigIntForm.Uint64()), nil } +// GetUint16 tries to retrieve an uint16 from an ABI Value. func GetUint16(value Value) (uint16, error) { if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 16 { return 0, fmt.Errorf("value type unmatch or size too large") @@ -986,6 +1062,7 @@ func GetUint16(value Value) (uint16, error) { return uint16(bigIntForm.Uint64()), nil } +// GetUint32 tries to retrieve an uint32 from an ABI Value. func GetUint32(value Value) (uint32, error) { if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 32 { return 0, fmt.Errorf("value type unmatch or size too large") @@ -997,6 +1074,7 @@ func GetUint32(value Value) (uint32, error) { return uint32(bigIntForm.Uint64()), nil } +// GetUint64 tries to retrieve an uint64 from an ABI Value. func GetUint64(value Value) (uint64, error) { if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 64 { return 0, fmt.Errorf("value type unmatch or size too large") @@ -1008,6 +1086,7 @@ func GetUint64(value Value) (uint64, error) { return bigIntForm.Uint64(), nil } +// GetUint tries to retrieve an big uint from an ABI Value. func GetUint(value Value) (*big.Int, error) { if value.valueType.typeFromEnum != Uint { return nil, fmt.Errorf("value type unmatch") @@ -1020,6 +1099,7 @@ func GetUint(value Value) (*big.Int, error) { return bigIntForm, nil } +// GetUfixed tries to retrieve an big rational number from an ABI Value. func GetUfixed(value Value) (*big.Rat, error) { if value.valueType.typeFromEnum != Ufixed { return nil, fmt.Errorf("value type unmatch, should be ufixed") @@ -1040,6 +1120,7 @@ func GetUfixed(value Value) (*big.Rat, error) { return ufixedForm, nil } +// GetString tries to retrieve a string from ABI Value. func GetString(value Value) (string, error) { if value.valueType.typeFromEnum != String { return "", fmt.Errorf("value type unmatch, should be ufixed") @@ -1048,6 +1129,7 @@ func GetString(value Value) (string, error) { return stringForm, nil } +// GetByte tries to retrieve a byte from ABI Value. func GetByte(value Value) (byte, error) { if value.valueType.typeFromEnum != Byte { return byte(0), fmt.Errorf("value type unmatch, should be bytes") @@ -1056,6 +1138,7 @@ func GetByte(value Value) (byte, error) { return bytesForm, nil } +// GetAddress tries to retrieve a [32]byte array from ABI Value. func GetAddress(value Value) ([32]byte, error) { if value.valueType.typeFromEnum != Address { return [32]byte{}, fmt.Errorf("value type unmatch, should be address") @@ -1064,6 +1147,7 @@ func GetAddress(value Value) ([32]byte, error) { return addressForm, nil } +// GetDynamicArrayByIndex takes an index and tries to retrieve the element ABI Value. func GetDynamicArrayByIndex(value Value, index uint16) (Value, error) { if value.valueType.typeFromEnum != ArrayDynamic { return Value{}, fmt.Errorf("value type unmatch, should be dynamic array") @@ -1075,6 +1159,7 @@ func GetDynamicArrayByIndex(value Value, index uint16) (Value, error) { return elements[index], nil } +// GetStaticArrayByIndex takes an index and tries to retrieve the element ABI Value. func GetStaticArrayByIndex(value Value, index uint16) (Value, error) { if value.valueType.typeFromEnum != ArrayStatic { return Value{}, fmt.Errorf("value type unmatch, should be static array") @@ -1086,6 +1171,7 @@ func GetStaticArrayByIndex(value Value, index uint16) (Value, error) { return elements[index], nil } +// GetTupleByIndex takes an index and tries to retrieve the eleemnt ABI Value. func GetTupleByIndex(value Value, index uint16) (Value, error) { if value.valueType.typeFromEnum != Tuple { return Value{}, fmt.Errorf("value type unmatch, should be tuple") @@ -1097,6 +1183,7 @@ func GetTupleByIndex(value Value, index uint16) (Value, error) { return elements[index], nil } +// GetBool tries to retrieve a boolean value from the ABI Value. func GetBool(value Value) (bool, error) { if value.valueType.typeFromEnum != Bool { return false, fmt.Errorf("value type unmatch, should be bool") diff --git a/cmd/goal/abi/abi_encode_test.go b/cmd/goal/abi/abi_encode_test.go index 3f54687f12..6eb574bbfe 100644 --- a/cmd/goal/abi/abi_encode_test.go +++ b/cmd/goal/abi/abi_encode_test.go @@ -1,3 +1,19 @@ +// 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 ( diff --git a/cmd/goal/abi/abi_type_test.go b/cmd/goal/abi/abi_type_test.go index 55998328a0..abf737cfcd 100644 --- a/cmd/goal/abi/abi_type_test.go +++ b/cmd/goal/abi/abi_type_test.go @@ -1,3 +1,19 @@ +// 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 ( diff --git a/go.mod b/go.mod index f779709580..4674a96af9 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 // indirect 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= From 80c1975e9ad3ad8949a6a9f614b8eff6fbb7ce91 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 Aug 2021 18:03:37 -0400 Subject: [PATCH 03/29] minor modification --- cmd/goal/abi/abi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/abi/abi.go b/cmd/goal/abi/abi.go index 53b237de91..fdca809852 100644 --- a/cmd/goal/abi/abi.go +++ b/cmd/goal/abi/abi.go @@ -584,7 +584,7 @@ func (v Value) Encode() ([]byte, error) { // 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 true { + for { curr := index + delta*until if typeList[curr].typeFromEnum == Bool { if curr != len(typeList)-1 && delta > 0 { From f5c0f21757506c6803d7d4cf99f8018ae6c07768 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 25 Aug 2021 18:49:02 -0400 Subject: [PATCH 04/29] add partition test --- cmd/goal/abi/abi_encode_test.go | 4 ++++ cmd/goal/abi/abi_type_test.go | 6 ++++++ go.mod | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/goal/abi/abi_encode_test.go b/cmd/goal/abi/abi_encode_test.go index 6eb574bbfe..e533d235dc 100644 --- a/cmd/goal/abi/abi_encode_test.go +++ b/cmd/goal/abi/abi_encode_test.go @@ -22,11 +22,13 @@ import ( "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) for intSize := 8; intSize <= 512; intSize += 8 { upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize)) for i := 0; i < 1000; i++ { @@ -216,6 +218,7 @@ func TestEncodeValid(t *testing.T) { } func TestDecodeValid(t *testing.T) { + partitiontest.PartitionTest(t) for intSize := 8; intSize <= 512; intSize += 8 { upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize)) for i := 0; i < 1000; i++ { @@ -407,6 +410,7 @@ func TestDecodeValid(t *testing.T) { } func TestDecodeInvalid(t *testing.T) { + partitiontest.PartitionTest(t) t.Run("corrupted static bool array decode", func(t *testing.T) { inputBase := []byte{0b11111111} arrayType := MakeStaticArrayType(MakeBoolType(), 9) diff --git a/cmd/goal/abi/abi_type_test.go b/cmd/goal/abi/abi_type_test.go index abf737cfcd..59db50efe8 100644 --- a/cmd/goal/abi/abi_type_test.go +++ b/cmd/goal/abi/abi_type_test.go @@ -24,12 +24,14 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) // TODO need a fuzz test for the parsing func TestMakeTypeValid(t *testing.T) { + partitiontest.PartitionTest(t) // uint for i := 8; i <= 512; i += 8 { uintType, _ := MakeUintType(uint16(i)) @@ -139,6 +141,7 @@ func TestMakeTypeValid(t *testing.T) { } func TestMakeTypeInvalid(t *testing.T) { + partitiontest.PartitionTest(t) // uint for i := 0; i <= 1000; i++ { randInput := rand.Uint32() @@ -165,6 +168,7 @@ func TestMakeTypeInvalid(t *testing.T) { } func TestTypeFromStringValid(t *testing.T) { + partitiontest.PartitionTest(t) // uint for i := 8; i <= 512; i += 8 { expected, _ := MakeUintType(uint16(i)) @@ -344,6 +348,7 @@ func TestTypeFromStringValid(t *testing.T) { } 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 { @@ -433,6 +438,7 @@ func generateTupleType(baseTypes []Type, tupleTypes []Type) Type { } func TestTypeMISC(t *testing.T) { + partitiontest.PartitionTest(t) rand.Seed(time.Now().Unix()) var testpool = []Type{ diff --git a/go.mod b/go.mod index 4674a96af9..09c57773d0 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +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 // indirect + 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 From bdd3c107a0e5145b272c18ffd03d9dcc58c9a2fa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 11:01:36 -0400 Subject: [PATCH 05/29] resolve review, need more testcase rewrite --- cmd/goal/abi/abi.go | 608 +++++++++++++++----------------- cmd/goal/abi/abi_encode_test.go | 67 ++-- cmd/goal/abi/abi_type_test.go | 162 +++++---- 3 files changed, 419 insertions(+), 418 deletions(-) diff --git a/cmd/goal/abi/abi.go b/cmd/goal/abi/abi.go index fdca809852..bc56b96765 100644 --- a/cmd/goal/abi/abi.go +++ b/cmd/goal/abi/abi.go @@ -23,7 +23,6 @@ import ( "regexp" "strconv" "strings" - "unicode" ) /* @@ -64,13 +63,13 @@ const ( // Type is the struct that stores information about an ABI value's type. type Type struct { - typeFromEnum BaseType - childTypes []Type + enumIndex BaseType + childTypes []Type // only can be applied to `uint` size or `ufixed` size - typeSize uint16 + size uint16 // only can be applied to `ufixed` precision - typePrecision uint16 + precision uint16 // length for static array / tuple /* @@ -78,19 +77,19 @@ type Type struct { 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 */ - // TODO may want to change back to uint32/uint64 + // 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.typeFromEnum { + switch t.enumIndex { case Uint: - return "uint" + strconv.Itoa(int(t.typeSize)) + return "uint" + strconv.Itoa(int(t.size)) case Byte: return "byte" case Ufixed: - return "ufixed" + strconv.Itoa(int(t.typeSize)) + "x" + strconv.Itoa(int(t.typePrecision)) + return "ufixed" + strconv.Itoa(int(t.size)) + "x" + strconv.Itoa(int(t.precision)) case Bool: return "bool" case ArrayStatic: @@ -108,7 +107,7 @@ func (t Type) String() string { } return "(" + strings.Join(typeStrings, ",") + ")" default: - return "Bruh you should not be here" + panic("Bruh you should not be here") } } @@ -121,20 +120,20 @@ func TypeFromString(str string) (Type, error) { return arrayArgType, err } return MakeDynamicArrayType(arrayArgType), nil - case strings.HasSuffix(str, "]") && len(str) >= 2 && unicode.IsDigit(rune(str[len(str)-2])): - stringMatches := regexp.MustCompile(`^[a-z\d\[\](),]+\[([1-9][\d]*)]$`).FindStringSubmatch(str) - // match the string itself, then array length - if len(stringMatches) != 2 { + case strings.HasSuffix(str, "]"): + stringMatches := regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`).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[1] - arrayLength, err := strconv.ParseUint(arrayLengthStr, 10, 32) + arrayLengthStr := stringMatches[2] + arrayLength, err := strconv.ParseUint(arrayLengthStr, 10, 16) if err != nil { return Type{}, err } // parse the array element type - arrayType, err := TypeFromString(str[:len(str)-(2+len(arrayLengthStr))]) + arrayType, err := TypeFromString(stringMatches[1]) if err != nil { return Type{}, err } @@ -144,11 +143,7 @@ func TypeFromString(str string) (Type, error) { if err != nil { return Type{}, fmt.Errorf("ill formed uint type: %s", str) } - uintTypeRes, err := MakeUintType(uint16(typeSize)) - if err != nil { - return Type{}, err - } - return uintTypeRes, nil + return MakeUintType(uint16(typeSize)) case str == "byte": return MakeByteType(), nil case strings.HasPrefix(str, "ufixed"): @@ -166,11 +161,7 @@ func TypeFromString(str string) (Type, error) { if err != nil { return Type{}, err } - ufixedTypeRes, err := MakeUFixedType(uint16(ufixedSize), uint16(ufixedPrecision)) - if err != nil { - return Type{}, err - } - return ufixedTypeRes, nil + return MakeUfixedType(uint16(ufixedSize), uint16(ufixedPrecision)) case str == "bool": return MakeBoolType(), nil case str == "address": @@ -178,9 +169,6 @@ func TypeFromString(str string) (Type, error) { case str == "string": return MakeStringType(), nil case len(str) > 2 && str[0] == '(' && str[len(str)-1] == ')': - if strings.Contains(str[1:len(str)-1], " ") { - return Type{}, fmt.Errorf("tuple should not contain space") - } tupleContent, err := parseTupleContent(str[1 : len(str)-1]) if err != nil { return Type{}, err @@ -193,14 +181,14 @@ func TypeFromString(str string) (Type, error) { } tupleTypes[i] = ti } - return MakeTupleType(tupleTypes), nil + return MakeTupleType(tupleTypes) default: return Type{}, fmt.Errorf("cannot convert a string %s to an ABI type", str) } } -// segmentIndex keeps track of the start and end of a segment in a string. -type segmentIndex struct{ left, right int } +// 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. @@ -208,7 +196,8 @@ func parseTupleContent(str string) ([]string, error) { // argument str is the content between parentheses of tuple, i.e. // (...... str ......) // ^ ^ - parenSegmentRecord, stack := make([]segmentIndex, 0), make([]int, 0) + parenSegmentRecord := make([]segment, 0) + stack := make([]int, 0) // get the most exterior parentheses segment (not overlapped by other parentheses) // illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"] for index, chr := range str { @@ -221,7 +210,7 @@ func parseTupleContent(str string) ([]string, error) { leftParenIndex := stack[len(stack)-1] stack = stack[:len(stack)-1] if len(stack) == 0 { - parenSegmentRecord = append(parenSegmentRecord, segmentIndex{ + parenSegmentRecord = append(parenSegmentRecord, segment{ left: leftParenIndex, right: index, }) @@ -240,15 +229,15 @@ func parseTupleContent(str string) ([]string, error) { // take out tuple-formed type str in tuple argument strCopied := str for i := len(parenSegmentRecord) - 1; i >= 0; i-- { - segment := parenSegmentRecord[i] - strCopied = strCopied[:segment.left] + strCopied[segment.right+1:] + parenSeg := parenSegmentRecord[i] + strCopied = strCopied[:parenSeg.left] + strCopied[parenSeg.right+1:] } // maintain list of empty strings as placeholders for tuple-formed type str - segments := strings.Split(strCopied, ",") + tupleStrSegs := strings.Split(strCopied, ",") emptyStrIndex := make([]int, 0) - for index, str := range segments { - if str == "" { + for index, segStr := range tupleStrSegs { + if segStr == "" { emptyStrIndex = append(emptyStrIndex, index) } } @@ -259,52 +248,52 @@ func parseTupleContent(str string) ([]string, error) { // replace back the tuple-formed type str for index, replaceIndex := range emptyStrIndex { - segments[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] + tupleStrSegs[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] } - return segments, nil + return tupleStrSegs, nil } // MakeUintType makes `Uint` ABI type by taking a type size argument. // The range of type size is [8, 512] and type size % 8 == 0. func MakeUintType(typeSize uint16) (Type, error) { if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { - return Type{}, fmt.Errorf("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) + return Type{}, fmt.Errorf("unsupported uint type size: %d", typeSize) } return Type{ - typeFromEnum: Uint, - typeSize: typeSize, + enumIndex: Uint, + size: typeSize, }, nil } // MakeByteType makes `Byte` ABI type. func MakeByteType() Type { return Type{ - typeFromEnum: Byte, + enumIndex: Byte, } } -// MakeUFixedType makes `UFixed` ABI type by taking type size and type precision as arguments. +// MakeUfixedType makes `UFixed` ABI type by taking type size and type precision as arguments. // The range of type size is [8, 512] and type size % 8 == 0. // The range of type precision is [1, 160]. -func MakeUFixedType(typeSize uint16, typePrecision uint16) (Type, error) { +func MakeUfixedType(typeSize uint16, typePrecision uint16) (Type, error) { if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { - return Type{}, fmt.Errorf("type uint size mod 8 = 0, range [8, 512], error typesize: %d", typeSize) + return Type{}, fmt.Errorf("unsupported ufixed type size: %d", typeSize) } if typePrecision > 160 || typePrecision < 1 { - return Type{}, fmt.Errorf("type uint precision range [1, 160]") + return Type{}, fmt.Errorf("unsupported ufixed type precision: %d", typePrecision) } return Type{ - typeFromEnum: Ufixed, - typeSize: typeSize, - typePrecision: typePrecision, + enumIndex: Ufixed, + size: typeSize, + precision: typePrecision, }, nil } // MakeBoolType makes `Bool` ABI type. func MakeBoolType() Type { return Type{ - typeFromEnum: Bool, + enumIndex: Bool, } } @@ -312,7 +301,7 @@ func MakeBoolType() Type { // array element type and array length as arguments. func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { return Type{ - typeFromEnum: ArrayStatic, + enumIndex: ArrayStatic, childTypes: []Type{argumentType}, staticLength: arrayLength, } @@ -321,88 +310,63 @@ func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { // MakeAddressType makes `Address` ABI type. func MakeAddressType() Type { return Type{ - typeFromEnum: Address, + enumIndex: Address, } } // MakeDynamicArrayType makes dynamic length array by taking array element type as argument. func MakeDynamicArrayType(argumentType Type) Type { return Type{ - typeFromEnum: ArrayDynamic, - childTypes: []Type{argumentType}, + enumIndex: ArrayDynamic, + childTypes: []Type{argumentType}, } } // MakeStringType makes `String` ABI type. func MakeStringType() Type { return Type{ - typeFromEnum: String, + enumIndex: String, } } // MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument. -func MakeTupleType(argumentTypes []Type) Type { +func MakeTupleType(argumentTypes []Type) (Type, error) { + if len(argumentTypes) >= (1<<16) || len(argumentTypes) == 0 { + return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") + } else if len(argumentTypes) == 0 { + return Type{}, fmt.Errorf("tuple type child type 0 error") + } return Type{ - typeFromEnum: Tuple, + enumIndex: 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 { - // assume t and t0 are well-formed - switch t.typeFromEnum { - case Uint: - return t.typeFromEnum == t0.typeFromEnum && t.typeSize == t0.typeSize - case Ufixed: - if t0.typeFromEnum != Ufixed { - return false - } else if t0.typePrecision != t.typePrecision || t0.typeSize != t.typeSize { - return false - } else { - return true - } - case ArrayStatic: - if t0.typeFromEnum != ArrayStatic { - return false - } else if len(t.childTypes) != len(t0.childTypes) || len(t0.childTypes) != 1 { - return false - } else if t.staticLength != t0.staticLength { - return false - } else { - return t.childTypes[0].Equal(t0.childTypes[0]) - } - case ArrayDynamic: - if t0.typeFromEnum != ArrayDynamic { - return false - } else if len(t.childTypes) != len(t0.childTypes) || len(t0.childTypes) != 1 { + if t.enumIndex != t0.enumIndex { + return false + } else if t.precision != t0.precision || t.size != t0.size { + return false + } else if t.staticLength != t0.staticLength { + return false + } else { + if len(t.childTypes) != len(t0.childTypes) { return false - } else { - return t.childTypes[0].Equal(t0.childTypes[0]) } - case Tuple: - if t0.typeFromEnum != Tuple { - return false - } else if t.staticLength != t0.staticLength || int(t.staticLength) != len(t0.childTypes) { - return false - } else { - for i := 0; i < int(t.staticLength); i++ { - compRes := t.childTypes[i].Equal(t0.childTypes[i]) - if !compRes { - return false - } + for i := 0; i < len(t.childTypes); i++ { + if !t.childTypes[i].Equal(t0.childTypes[i]) { + return false } - return true } - default: - return t.typeFromEnum == t0.typeFromEnum } + return true } // IsDynamic method decides if an ABI type is dynamic or static. func (t Type) IsDynamic() bool { - switch t.typeFromEnum { + switch t.enumIndex { case ArrayStatic: return t.childTypes[0].IsDynamic() case ArrayDynamic, String: @@ -413,7 +377,7 @@ func (t Type) IsDynamic() bool { return true } } - return false + fallthrough default: return false } @@ -421,20 +385,23 @@ func (t Type) IsDynamic() bool { // ByteLen method calculates the byte length of a static ABI type. func (t Type) ByteLen() (int, error) { - if t.IsDynamic() { - return -1, fmt.Errorf("dynamic type") - } - - switch t.typeFromEnum { + switch t.enumIndex { case Address: return 32, nil case Byte: return 1, nil case Uint, Ufixed: - return int(t.typeSize / 8), nil + return int(t.size / 8), nil case Bool: return 1, nil case ArrayStatic: + if t.childTypes[0].enumIndex == 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 @@ -442,23 +409,39 @@ func (t Type) ByteLen() (int, error) { return int(t.staticLength) * elemByteLen, nil case Tuple: size := 0 - for _, childT := range t.childTypes { - childByteSize, err := childT.ByteLen() - if err != nil { - return -1, err + for i := 0; i < len(t.childTypes); i++ { + if t.childTypes[i].enumIndex == Bool { + // search previous bool + before := findBoolLR(t.childTypes, i, -1) + // search after bool + after := findBoolLR(t.childTypes, i, 1) + // append to heads and tails + if before%8 != 0 { + return -1, fmt.Errorf("expected before has number of bool mod 8 = 0") + } + if after > 7 { + after = 7 + } + i += after + size++ + } else { + childByteSize, err := t.childTypes[i].ByteLen() + if err != nil { + return -1, err + } + size += childByteSize } - size += childByteSize } return size, nil default: - return -1, fmt.Errorf("bruh you should not be here") + return -1, fmt.Errorf("%s is a dynamic type", t.String()) } } // Value struct is the ABI Value, holding ABI Type information and the ABI value representation. type Value struct { - valueType Type - value interface{} + abiType Type + value interface{} } // arrayToTuple casts an array-like ABI Value into an ABI Value of Tuple type. @@ -467,9 +450,9 @@ func (v Value) arrayToTuple() (Value, error) { var childT []Type var valueArr []Value - switch v.valueType.typeFromEnum { + switch v.abiType.enumIndex { case String: - strValue, err := GetString(v) + strValue, err := v.GetString() if err != nil { return Value{}, err } @@ -480,7 +463,7 @@ func (v Value) arrayToTuple() (Value, error) { valueArr[i] = MakeByte(strByte[i]) } case Address: - addr, err := GetAddress(v) + addr, err := v.GetAddress() if err != nil { return Value{}, err } @@ -490,55 +473,58 @@ func (v Value) arrayToTuple() (Value, error) { valueArr[i] = MakeByte(addr[i]) } case ArrayStatic: - childT = make([]Type, v.valueType.staticLength) - for i := 0; i < int(v.valueType.staticLength); i++ { - childT[i] = v.valueType.childTypes[0] + 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.valueType.childTypes[0] + 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{ - valueType: MakeTupleType(childT), - value: valueArr, + 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.valueType.typeFromEnum { + switch v.abiType.enumIndex { case Uint: - bigIntValue, err := GetUint(v) + 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.valueType.typeSize/8-uint16(len(bigIntBytes))) + buffer := make([]byte, v.abiType.size/8-uint16(len(bigIntBytes))) buffer = append(buffer, bigIntBytes...) return buffer, nil case Ufixed: - ufixedValue, err := GetUfixed(v) + ufixedValue, err := v.GetUfixed() if err != nil { return []byte{}, err } - denomSize := big.NewInt(1).Exp(big.NewInt(10), big.NewInt(int64(v.valueType.typePrecision)), nil) - denomRat := big.NewRat(1, 1).SetFrac(denomSize, big.NewInt(1)) - numRat := denomRat.Mul(denomRat, ufixedValue) - encodeVal := numRat.Num() - encodeBuffer := encodeVal.Bytes() - buffer := make([]byte, v.valueType.typeSize/8-uint16(len(encodeBuffer))) + // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use fillbytes + encodeBuffer := ufixedValue.Bytes() + buffer := make([]byte, v.abiType.size/8-uint16(len(encodeBuffer))) buffer = append(buffer, encodeBuffer...) return buffer, nil case Bool: - boolValue, err := GetBool(v) + boolValue, err := v.GetBool() if err != nil { return []byte{}, err } @@ -547,7 +533,7 @@ func (v Value) Encode() ([]byte, error) { } return []byte{0x00}, nil case Byte: - bytesValue, err := GetByte(v) + bytesValue, err := v.GetByte() if err != nil { return []byte{}, nil } @@ -563,7 +549,7 @@ func (v Value) Encode() ([]byte, error) { if err != nil { return []byte{}, err } - length := len(convertedTuple.valueType.childTypes) + length := len(convertedTuple.abiType.childTypes) lengthEncode := make([]byte, 2) binary.BigEndian.PutUint16(lengthEncode, uint16(length)) @@ -586,7 +572,7 @@ func findBoolLR(typeList []Type, index int, delta int) int { until := 0 for { curr := index + delta*until - if typeList[curr].typeFromEnum == Bool { + if typeList[curr].enumIndex == Bool { if curr != len(typeList)-1 && delta > 0 { until++ } else if curr > 0 && delta < 0 { @@ -609,10 +595,10 @@ func compressMultipleBool(valueList []Value) (uint8, error) { return 0, fmt.Errorf("value list passed in should be less than length 8") } for i := 0; i < len(valueList); i++ { - if valueList[i].valueType.typeFromEnum != Bool { + if valueList[i].abiType.enumIndex != Bool { return 0, fmt.Errorf("bool type not matching in compressMultipleBool") } - boolVal, err := GetBool(valueList[i]) + boolVal, err := valueList[i].GetBool() if err != nil { return 0, err } @@ -625,15 +611,20 @@ func compressMultipleBool(valueList []Value) (uint8, error) { // tupleEncoding encodes an ABI value of tuple type into an ABI encoded byte string. func tupleEncoding(v Value) ([]byte, error) { - if v.valueType.typeFromEnum != Tuple { - return []byte{}, fmt.Errorf("tupe not supported in tupleEncoding") + if v.abiType.enumIndex != 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") } - heads, tails := make([][]byte, len(v.valueType.childTypes)), make([][]byte, len(v.valueType.childTypes)) - isDynamicIndex := make(map[int]bool) tupleElems := v.value.([]Value) - for i := 0; i < len(v.valueType.childTypes); i++ { - switch tupleElems[i].valueType.IsDynamic() { - case true: + if len(tupleElems) != len(v.abiType.childTypes) { + return []byte{}, fmt.Errorf("tuple abi child type number unmatch with tuple argument number") + } + heads, tails := make([][]byte, len(v.abiType.childTypes)), 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() { headsPlaceholder := []byte{0x00, 0x00} heads[i] = headsPlaceholder isDynamicIndex[i] = true @@ -642,12 +633,12 @@ func tupleEncoding(v Value) ([]byte, error) { return []byte{}, err } tails[i] = tailEncoding - case false: - if tupleElems[i].valueType.typeFromEnum == Bool { + } else { + if tupleElems[i].abiType.enumIndex == Bool { // search previous bool - before := findBoolLR(v.valueType.childTypes, i, -1) + before := findBoolLR(v.abiType.childTypes, i, -1) // search after bool - after := findBoolLR(v.valueType.childTypes, i, 1) + 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") @@ -661,14 +652,12 @@ func tupleEncoding(v Value) ([]byte, error) { } heads[i] = []byte{compressed} i += after - tails[i] = nil } else { encodeTi, err := tupleElems[i].Encode() if err != nil { return []byte{}, err } heads[i] = encodeTi - tails[i] = nil } isDynamicIndex[i] = false } @@ -684,13 +673,16 @@ func tupleEncoding(v Value) ([]byte, error) { for i := 0; i < len(heads); i++ { if isDynamicIndex[i] { 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)) } tailCurrLength += len(tails[i]) } head, tail := make([]byte, 0), make([]byte, 0) - for i := 0; i < len(v.valueType.childTypes); i++ { + for i := 0; i < len(v.abiType.childTypes); i++ { head = append(head, heads[i]...) tail = append(tail, tails[i]...) } @@ -700,26 +692,23 @@ func tupleEncoding(v Value) ([]byte, error) { // 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.typeFromEnum { + switch valueType.enumIndex { case Uint: - if len(valueByte) != int(valueType.typeSize)/8 { + if len(valueByte) != int(valueType.size)/8 { return Value{}, - fmt.Errorf("uint size %d byte, given byte size unmatch", int(valueType.typeSize)/8) + fmt.Errorf("uint%d decode: expected byte length %d, but got byte length %d", + valueType.size, valueType.size/8, len(valueByte)) } uintValue := big.NewInt(0).SetBytes(valueByte) - return MakeUint(uintValue, valueType.typeSize) + return MakeUint(uintValue, valueType.size) case Ufixed: - if len(valueByte) != int(valueType.typeSize)/8 { + if len(valueByte) != int(valueType.size)/8 { return Value{}, - fmt.Errorf("ufixed size %d byte, given byte size unmatch", int(valueType.typeSize)/8) + fmt.Errorf("ufixed%dx%d decode: expected length %d, got byte length %d", + valueType.size, valueType.precision, valueType.size/8, len(valueByte)) } ufixedNumerator := big.NewInt(0).SetBytes(valueByte) - ufixedDenominator := big.NewInt(0).Exp( - big.NewInt(10), big.NewInt(int64(valueType.typePrecision)), - nil, - ) - ufixedValue := big.NewRat(1, 1).SetFrac(ufixedNumerator, ufixedDenominator) - return MakeUfixed(ufixedValue, valueType.typeSize, valueType.typePrecision) + return MakeUfixed(ufixedNumerator, valueType.size, valueType.precision) case Bool: if len(valueByte) != 1 { return Value{}, fmt.Errorf("boolean byte should be length 1 byte") @@ -736,12 +725,15 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { for i := 0; i < int(valueType.staticLength); i++ { childT[i] = valueType.childTypes[0] } - converted := MakeTupleType(childT) + converted, err := MakeTupleType(childT) + if err != nil { + return Value{}, err + } tupleDecoded, err := tupleDecoding(valueByte, converted) if err != nil { return Value{}, err } - tupleDecoded.valueType = valueType + tupleDecoded.abiType = valueType return tupleDecoded, nil case Address: if len(valueByte) != 32 { @@ -759,12 +751,15 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { for i := 0; i < int(dynamicLen); i++ { childT[i] = valueType.childTypes[0] } - converted := MakeTupleType(childT) + converted, err := MakeTupleType(childT) + if err != nil { + return Value{}, err + } tupleDecoded, err := tupleDecoding(valueByte[2:], converted) if err != nil { return Value{}, err } - tupleDecoded.valueType = valueType + tupleDecoded.abiType = valueType return tupleDecoded, nil case String: if len(valueByte) < 2 { @@ -779,14 +774,14 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { case Tuple: return tupleDecoding(valueByte, valueType) default: - return Value{}, fmt.Errorf("bruh you should not be here in decoding: unknown type error") + 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, valuePartition := make([]segmentIndex, 0), make([][]byte, 0) + dynamicSegments, valuePartition := make([]segment, 0), make([][]byte, 0) iterIndex := 0 for i := 0; i < len(valueType.childTypes); i++ { if valueType.childTypes[i].IsDynamic() { @@ -797,7 +792,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { if len(dynamicSegments) > 0 { dynamicSegments[len(dynamicSegments)-1].right = int(dynamicIndex) - 1 } - dynamicSegments = append(dynamicSegments, segmentIndex{ + dynamicSegments = append(dynamicSegments, segment{ left: int(dynamicIndex), right: -1, }) @@ -805,7 +800,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { iterIndex += 2 } else { // if bool ... - if valueType.childTypes[i].typeFromEnum == Bool { + if valueType.childTypes[i].enumIndex == Bool { // search previous bool before := findBoolLR(valueType.childTypes, i, -1) // search after bool @@ -816,9 +811,8 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } // parse bool in a byte to multiple byte strings for boolIndex := uint(0); boolIndex <= uint(after); boolIndex++ { - // each time check the significant bit, from left to right - boolValue := valueBytes[iterIndex] << boolIndex - if boolValue >= 0x80 { + boolMask := 0x80 >> boolIndex + if valueBytes[iterIndex]&byte(boolMask) > 0 { valuePartition = append(valuePartition, []byte{0x80}) } else { valuePartition = append(valuePartition, []byte{0x00}) @@ -851,9 +845,9 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { // check segment indices are valid segIndexArr := make([]int, len(dynamicSegments)*2) - for index, segment := range dynamicSegments { - segIndexArr[index*2] = segment.left - segIndexArr[index*2+1] = segment.right + for index, seg := range dynamicSegments { + segIndexArr[index*2] = seg.left + segIndexArr[index*2+1] = seg.right } for i := 0; i < len(segIndexArr); i++ { if i%2 == 1 { @@ -884,8 +878,8 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { values = append(values, valueTi) } return Value{ - valueType: valueType, - value: values, + abiType: valueType, + value: values, }, nil } @@ -925,125 +919,125 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { return Value{}, fmt.Errorf("passed value larger than uint size %d", size) } return Value{ - valueType: typeUint, - value: value, + abiType: typeUint, + value: value, }, nil } // MakeUfixed takes a big rational number representation, a type size, and a type precision, // and returns an ABI Value of ABI UFixedx -func MakeUfixed(value *big.Rat, size uint16, precision uint16) (Value, error) { - ufixedValueType, err := MakeUFixedType(size, precision) +func MakeUfixed(value *big.Int, size uint16, precision uint16) (Value, error) { + ufixedValueType, err := MakeUfixedType(size, precision) if err != nil { - return Value{}, nil - } - denomSize := big.NewInt(0).Exp( - big.NewInt(10), big.NewInt(int64(precision)), - nil, - ) - numUpperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) - ufixedLimit := big.NewRat(1, 1).SetFrac(numUpperLimit, denomSize) - if value.Denom().Cmp(denomSize) > 0 { - return Value{}, fmt.Errorf("value precision overflow") + return Value{}, err } - if value.Cmp(big.NewRat(0, 1)) < 0 || value.Cmp(ufixedLimit) >= 0 { - return Value{}, fmt.Errorf("ufixed value out of scope") + uintVal, err := MakeUint(value, size) + if err != nil { + return Value{}, err } - return Value{ - valueType: ufixedValueType, - value: value, - }, nil + uintVal.abiType = ufixedValueType + return uintVal, nil } // MakeString takes a string and returns an ABI String type Value. func MakeString(value string) Value { return Value{ - valueType: MakeStringType(), - value: value, + abiType: MakeStringType(), + value: value, } } // MakeByte takes a byte and returns an ABI Byte type value. func MakeByte(value byte) Value { return Value{ - valueType: MakeByteType(), - value: 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{ - valueType: MakeAddressType(), - value: value, + abiType: MakeAddressType(), + value: value, } } -// MakeDynamicArray takes an array of ABI value of elemType, -// and returns an ABI dynamic length array value. -func MakeDynamicArray(values []Value, elemType Type) (Value, error) { +// MakeDynamicArray takes an array of ABI value and returns an ABI dynamic length array value. +func MakeDynamicArray(values []Value) (Value, error) { + if len(values) >= (1 << 16) { + return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") + } else if len(values) == 0 { + return Value{}, fmt.Errorf("dynamic array make error: 0 argument passed in") + } for i := 0; i < len(values); i++ { - if !values[i].valueType.Equal(elemType) { + if !values[i].abiType.Equal(values[0].abiType) { return Value{}, fmt.Errorf("type mismatch: %s and %s", - values[i].valueType.String(), elemType.String()) + values[i].abiType.String(), values[0].abiType.String()) } } return Value{ - valueType: MakeDynamicArrayType(elemType), - value: values, + abiType: MakeDynamicArrayType(values[0].abiType), + value: values, }, nil } -// MakeStaticArray takes an array of ABI value of elemType, -// and returns an ABI static length array value. -func MakeStaticArray(values []Value, elemType Type) (Value, error) { +// MakeStaticArray takes an array of ABI value and returns an ABI static length array value. +func MakeStaticArray(values []Value) (Value, error) { + if len(values) >= (1 << 16) { + return Value{}, fmt.Errorf("static array make error: pass in argument number larger than 2^16") + } else if len(values) == 0 { + return Value{}, fmt.Errorf("static array make error: 0 argument passed in") + } for i := 0; i < len(values); i++ { - if !values[i].valueType.Equal(elemType) { + if !values[i].abiType.Equal(values[0].abiType) { return Value{}, fmt.Errorf("type mismatch: %s and %s", - values[i].valueType.String(), elemType.String()) + values[i].abiType.String(), values[0].abiType.String()) } } return Value{ - valueType: MakeStaticArrayType(elemType, uint16(len(values))), - value: values, + abiType: MakeStaticArrayType(values[0].abiType, uint16(len(values))), + value: values, }, nil } -// MakeTuple takes an array of ABI values and an array of ABI types, -// and returns an ABI tuple value. -func MakeTuple(values []Value, tupleType []Type) (Value, error) { - if len(values) != len(tupleType) { - return Value{}, fmt.Errorf("tuple make: tuple element number unmatch with tuple type number") - } - if len(values) == 0 { - return Value{}, fmt.Errorf("empty tuple") +// MakeTuple takes an array of ABI values and returns an ABI tuple value. +func MakeTuple(values []Value) (Value, error) { + if len(values) >= (1 << 16) { + return Value{}, fmt.Errorf("tuple make error: pass in argument number larger than 2^16") + } else if len(values) == 0 { + return Value{}, fmt.Errorf("tuple make error: 0 argument passed in") } + tupleType := make([]Type, len(values)) for i := 0; i < len(values); i++ { - if !values[i].valueType.Equal(tupleType[i]) { - return Value{}, fmt.Errorf("type mismatch: %s and %s", - values[i].valueType.String(), tupleType[i].String()) - } + tupleType[i] = values[i].abiType } + + castedTupleType, err := MakeTupleType(tupleType) + if err != nil { + return Value{}, err + } + return Value{ - valueType: MakeTupleType(tupleType), - value: values, + abiType: castedTupleType, + value: values, }, nil } // MakeBool takes a boolean value and returns an ABI bool value. func MakeBool(value bool) Value { return Value{ - valueType: MakeBoolType(), - value: value, + abiType: MakeBoolType(), + value: value, } } // GetUint8 tries to retreve an uint8 from an ABI Value. -func GetUint8(value Value) (uint8, error) { - if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 8 { +func (v Value) GetUint8() (uint8, error) { + if v.abiType.enumIndex != Uint || v.abiType.size > 8 { return 0, fmt.Errorf("value type unmatch or size too large") } - bigIntForm, err := GetUint(value) + bigIntForm, err := v.GetUint() if err != nil { return 0, err } @@ -1051,11 +1045,11 @@ func GetUint8(value Value) (uint8, error) { } // GetUint16 tries to retrieve an uint16 from an ABI Value. -func GetUint16(value Value) (uint16, error) { - if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 16 { +func (v Value) GetUint16() (uint16, error) { + if v.abiType.enumIndex != Uint || v.abiType.size > 16 { return 0, fmt.Errorf("value type unmatch or size too large") } - bigIntForm, err := GetUint(value) + bigIntForm, err := v.GetUint() if err != nil { return 0, err } @@ -1063,11 +1057,11 @@ func GetUint16(value Value) (uint16, error) { } // GetUint32 tries to retrieve an uint32 from an ABI Value. -func GetUint32(value Value) (uint32, error) { - if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 32 { +func (v Value) GetUint32() (uint32, error) { + if v.abiType.enumIndex != Uint || v.abiType.size > 32 { return 0, fmt.Errorf("value type unmatch or size too large") } - bigIntForm, err := GetUint(value) + bigIntForm, err := v.GetUint() if err != nil { return 0, err } @@ -1075,11 +1069,11 @@ func GetUint32(value Value) (uint32, error) { } // GetUint64 tries to retrieve an uint64 from an ABI Value. -func GetUint64(value Value) (uint64, error) { - if value.valueType.typeFromEnum != Uint || value.valueType.typeSize > 64 { +func (v Value) GetUint64() (uint64, error) { + if v.abiType.enumIndex != Uint || v.abiType.size > 64 { return 0, fmt.Errorf("value type unmatch or size too large") } - bigIntForm, err := GetUint(value) + bigIntForm, err := v.GetUint() if err != nil { return 0, err } @@ -1087,12 +1081,12 @@ func GetUint64(value Value) (uint64, error) { } // GetUint tries to retrieve an big uint from an ABI Value. -func GetUint(value Value) (*big.Int, error) { - if value.valueType.typeFromEnum != Uint { +func (v Value) GetUint() (*big.Int, error) { + if v.abiType.enumIndex != Uint { return nil, fmt.Errorf("value type unmatch") } - bigIntForm := value.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(value.valueType.typeSize)) + bigIntForm := v.value.(*big.Int) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.abiType.size)) if sizeThreshold.Cmp(bigIntForm) <= 0 { return nil, fmt.Errorf("value is larger than uint size") } @@ -1100,94 +1094,70 @@ func GetUint(value Value) (*big.Int, error) { } // GetUfixed tries to retrieve an big rational number from an ABI Value. -func GetUfixed(value Value) (*big.Rat, error) { - if value.valueType.typeFromEnum != Ufixed { +func (v Value) GetUfixed() (*big.Int, error) { + if v.abiType.enumIndex != Ufixed { return nil, fmt.Errorf("value type unmatch, should be ufixed") } - ufixedForm := value.value.(*big.Rat) - numinatorSize := big.NewInt(0).Lsh(big.NewInt(1), uint(value.valueType.typeSize)) - denomSize := big.NewInt(0).Exp( - big.NewInt(10), big.NewInt(int64(value.valueType.typePrecision)), - nil, - ) - ufixedLimit := big.NewRat(1, 1).SetFrac(numinatorSize, denomSize) - if ufixedForm.Denom().Cmp(denomSize) > 0 { - return nil, fmt.Errorf("denominator size overflow") - } - if ufixedForm.Cmp(big.NewRat(0, 1)) < 0 || ufixedForm.Cmp(ufixedLimit) >= 0 { - return nil, fmt.Errorf("ufixed < 0 or ufixed larger than limit") + bigIntForm := v.value.(*big.Int) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.abiType.size)) + if sizeThreshold.Cmp(bigIntForm) <= 0 { + return nil, fmt.Errorf("value is larger than ufixed size") } - return ufixedForm, nil + return bigIntForm, nil } // GetString tries to retrieve a string from ABI Value. -func GetString(value Value) (string, error) { - if value.valueType.typeFromEnum != String { +func (v Value) GetString() (string, error) { + if v.abiType.enumIndex != String { return "", fmt.Errorf("value type unmatch, should be ufixed") } - stringForm := value.value.(string) + stringForm := v.value.(string) return stringForm, nil } // GetByte tries to retrieve a byte from ABI Value. -func GetByte(value Value) (byte, error) { - if value.valueType.typeFromEnum != Byte { +func (v Value) GetByte() (byte, error) { + if v.abiType.enumIndex != Byte { return byte(0), fmt.Errorf("value type unmatch, should be bytes") } - bytesForm := value.value.(byte) + bytesForm := v.value.(byte) return bytesForm, nil } // GetAddress tries to retrieve a [32]byte array from ABI Value. -func GetAddress(value Value) ([32]byte, error) { - if value.valueType.typeFromEnum != Address { +func (v Value) GetAddress() ([32]byte, error) { + if v.abiType.enumIndex != Address { return [32]byte{}, fmt.Errorf("value type unmatch, should be address") } - addressForm := value.value.([32]byte) + addressForm := v.value.([32]byte) return addressForm, nil } -// GetDynamicArrayByIndex takes an index and tries to retrieve the element ABI Value. -func GetDynamicArrayByIndex(value Value, index uint16) (Value, error) { - if value.valueType.typeFromEnum != ArrayDynamic { - return Value{}, fmt.Errorf("value type unmatch, should be dynamic array") - } - elements := value.value.([]Value) - if int(index) >= len(elements) { - return Value{}, fmt.Errorf("dynamic array cannot get element: index out of scope") - } - return elements[index], nil -} - -// GetStaticArrayByIndex takes an index and tries to retrieve the element ABI Value. -func GetStaticArrayByIndex(value Value, index uint16) (Value, error) { - if value.valueType.typeFromEnum != ArrayStatic { - return Value{}, fmt.Errorf("value type unmatch, should be static array") - } - if index >= value.valueType.staticLength { - return Value{}, fmt.Errorf("static array cannot get element: index out of scope") - } - elements := value.value.([]Value) - return elements[index], nil -} - -// GetTupleByIndex takes an index and tries to retrieve the eleemnt ABI Value. -func GetTupleByIndex(value Value, index uint16) (Value, error) { - if value.valueType.typeFromEnum != Tuple { - return Value{}, fmt.Errorf("value type unmatch, should be tuple") - } - elements := value.value.([]Value) - if int(index) >= len(elements) { - return Value{}, fmt.Errorf("tuple cannot get element: index out of scope") +// GetValueByIndex retrieve value element by the index passed in +func (v Value) GetValueByIndex(index uint16) (Value, error) { + switch v.abiType.enumIndex { + 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") } - return elements[index], nil } // GetBool tries to retrieve a boolean value from the ABI Value. -func GetBool(value Value) (bool, error) { - if value.valueType.typeFromEnum != Bool { +func (v Value) GetBool() (bool, error) { + if v.abiType.enumIndex != Bool { return false, fmt.Errorf("value type unmatch, should be bool") } - boolForm := value.value.(bool) + boolForm := v.value.(bool) return boolForm, nil } diff --git a/cmd/goal/abi/abi_encode_test.go b/cmd/goal/abi/abi_encode_test.go index e533d235dc..b4b5df3716 100644 --- a/cmd/goal/abi/abi_encode_test.go +++ b/cmd/goal/abi/abi_encode_test.go @@ -62,13 +62,11 @@ func TestEncodeValid(t *testing.T) { big.NewInt(1).Neg(big.NewInt(1)), ) for precision := 1; precision <= 160; precision++ { - denomLimit := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(precision)), nil) for i := 0; i < 10; i++ { randomInt, err := rand.Int(rand.Reader, upperLimit) require.NoError(t, err, "cryptographic random int init fail") - ufixedRational := big.NewRat(1, 1).SetFrac(randomInt, denomLimit) - valueUfixed, err := MakeUfixed(ufixedRational, uint16(size), uint16(precision)) + valueUfixed, err := MakeUfixed(randomInt, uint16(size), uint16(precision)) require.NoError(t, err, "makeUfixed Fail") encodedUfixed, err := valueUfixed.Encode() @@ -80,12 +78,12 @@ func TestEncodeValid(t *testing.T) { require.Equal(t, buffer, encodedUfixed, "encode ufixed not match with expected") } // (2^[size] - 1) / (10^[precision]) test - ufixedLargest := big.NewRat(1, 1).SetFrac(largest, denomLimit) - ufixedLargestValue, err := MakeUfixed(ufixedLargest, uint16(size), uint16(precision)) + 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") + require.Equal(t, largest.Bytes(), ufixedLargestEncode, + "encode ufixed largest do not match with expected") } } @@ -149,7 +147,7 @@ func TestEncodeValid(t *testing.T) { expected := []byte{ 0b10011000, } - boolArr, err := MakeStaticArray(arrayElems, MakeBoolType()) + 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") @@ -165,7 +163,7 @@ func TestEncodeValid(t *testing.T) { expected := []byte{ 0b00011010, 0b10100000, } - boolArr, err := MakeStaticArray(arrayElems, MakeBoolType()) + 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") @@ -181,7 +179,7 @@ func TestEncodeValid(t *testing.T) { expected := []byte{ 0x00, 0x0A, 0b01010101, 0b01000000, } - boolArr, err := MakeDynamicArray(arrayElems, MakeBoolType()) + boolArr, err := MakeDynamicArray(arrayElems) 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") @@ -207,9 +205,7 @@ func TestEncodeValid(t *testing.T) { 0x00, 0x03, byte('A'), byte('B'), byte('C'), 0x00, 0x03, byte('D'), byte('E'), byte('F'), } - stringTuple, err := MakeTuple(tupleElems, []Type{ - MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), - }) + 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") @@ -239,19 +235,17 @@ func TestDecodeValid(t *testing.T) { for size := 8; size <= 512; size += 8 { upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) for precision := 1; precision <= 160; precision++ { - denomLimit := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(precision)), nil) for i := 0; i < 10; i++ { randomInt, err := rand.Int(rand.Reader, upperLimit) require.NoError(t, err, "cryptographic random int init fail") - ufixedRational := big.NewRat(1, 1).SetFrac(randomInt, denomLimit) - valueUfixed, err := MakeUfixed(ufixedRational, uint16(size), uint16(precision)) + 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)) + ufixedType, err := MakeUfixedType(uint16(size), uint16(precision)) require.NoError(t, err, "ufixed type make fail") decodedUfixed, err := Decode(encodedUfixed, ufixedType) @@ -318,7 +312,7 @@ func TestDecodeValid(t *testing.T) { for index, bVal := range inputBase { arrayElems[index] = MakeBool(bVal) } - expected, err := MakeStaticArray(arrayElems, MakeBoolType()) + 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") @@ -331,7 +325,7 @@ func TestDecodeValid(t *testing.T) { for index, bVal := range inputBase { arrayElems[index] = MakeBool(bVal) } - expected, err := MakeStaticArray(arrayElems, MakeBoolType()) + expected, err := MakeStaticArray(arrayElems) require.NoError(t, err, "make expected value should not return error") actual, err := Decode( []byte{ @@ -353,7 +347,7 @@ func TestDecodeValid(t *testing.T) { } uintT, err := MakeUintType(64) require.NoError(t, err, "make uint64 type should not return error") - expected, err := MakeStaticArray(arrayElems, uintT) + 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") @@ -368,7 +362,7 @@ func TestDecodeValid(t *testing.T) { for index, bVal := range inputBase { arrayElems[index] = MakeBool(bVal) } - expected, err := MakeDynamicArray(arrayElems, MakeBoolType()) + expected, err := MakeDynamicArray(arrayElems) require.NoError(t, err, "make expected value should not return error") inputEncoded := []byte{ 0x00, 0x0A, 0b01010101, 0b01000000, @@ -397,13 +391,20 @@ func TestDecodeValid(t *testing.T) { tupleElems[index] = MakeBool(temp) } } - expected, err := MakeTuple(tupleElems, []Type{ - MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), - }) + expected, err := MakeTuple(tupleElems) require.NoError(t, err, "make expected value should not return error") - actual, err := Decode(inputEncode, MakeTupleType([]Type{ - MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), - })) + actual, err := Decode( + inputEncode, + Type{ + enumIndex: 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") }) @@ -497,9 +498,17 @@ func TestDecodeInvalid(t *testing.T) { tupleElems[index] = MakeBool(temp) } } - _, err := Decode(inputEncode, MakeTupleType([]Type{ - MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeStringType(), - })) + _, err := Decode( + inputEncode, + Type{ + enumIndex: Tuple, + childTypes: []Type{ + MakeStringType(), + MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), + MakeStringType(), + }, + }, + ) require.Error(t, err, "corrupted decoding dynamic tuple should return error") }) } diff --git a/cmd/goal/abi/abi_type_test.go b/cmd/goal/abi/abi_type_test.go index 59db50efe8..463f5af930 100644 --- a/cmd/goal/abi/abi_type_test.go +++ b/cmd/goal/abi/abi_type_test.go @@ -42,7 +42,7 @@ func TestMakeTypeValid(t *testing.T) { // ufixed for i := 8; i <= 512; i += 8 { for j := 1; j <= 160; j++ { - ufixedType, _ := MakeUFixedType(uint16(i), uint16(j)) + ufixedType, _ := MakeUfixedType(uint16(i), uint16(j)) expected := "ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j) actual := ufixedType.String() require.Equal(t, expected, actual, @@ -63,8 +63,8 @@ func TestMakeTypeValid(t *testing.T) { { input: MakeDynamicArrayType( Type{ - typeFromEnum: Uint, - typeSize: uint16(32), + enumIndex: Uint, + size: uint16(32), }, ), testType: "dynamic array", @@ -82,9 +82,9 @@ func TestMakeTypeValid(t *testing.T) { { input: MakeStaticArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: uint16(128), - typePrecision: uint16(10), + enumIndex: Ufixed, + size: uint16(128), + precision: uint16(10), }, uint16(100), ), @@ -104,29 +104,33 @@ func TestMakeTypeValid(t *testing.T) { }, // tuple type { - input: MakeTupleType( - []Type{ + input: Type{ + enumIndex: Tuple, + childTypes: []Type{ { - typeFromEnum: Uint, - typeSize: uint16(32), + enumIndex: Uint, + size: uint16(32), }, - MakeTupleType( - []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: uint16(256), - typePrecision: uint16(10), + enumIndex: Ufixed, + size: uint16(256), + precision: uint16(10), }, ), }, - ), + staticLength: 4, + }, MakeDynamicArrayType(MakeByteType()), }, - ), + staticLength: 3, + }, testType: "tuple type", expected: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", }, @@ -162,7 +166,7 @@ func TestMakeTypeInvalid(t *testing.T) { for randPrecision >= 1 && randPrecision <= 160 { randPrecision = rand.Uint32() } - _, err := MakeUFixedType(uint16(randSize), uint16(randPrecision)) + _, err := MakeUfixedType(uint16(randSize), uint16(randPrecision)) require.Error(t, err, "MakeUintType: should throw error on size input %d", randSize) } } @@ -180,7 +184,7 @@ func TestTypeFromStringValid(t *testing.T) { // ufixed for i := 8; i <= 512; i += 8 { for j := 1; j <= 160; j++ { - expected, _ := MakeUFixedType(uint16(i), uint16(j)) + expected, _ := MakeUfixedType(uint16(i), uint16(j)) actual, err := TypeFromString("ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j)) require.Equal(t, nil, err, "TypeFromString ufixed parsing error: %s", expected.String()) require.Equal(t, expected, actual, @@ -199,16 +203,16 @@ func TestTypeFromStringValid(t *testing.T) { { input: "uint256[]", testType: "dynamic array", - expected: MakeDynamicArrayType(Type{typeFromEnum: Uint, typeSize: 256}), + expected: MakeDynamicArrayType(Type{enumIndex: Uint, size: 256}), }, { input: "ufixed256x64[]", testType: "dynamic array", expected: MakeDynamicArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: 256, - typePrecision: 64, + enumIndex: Ufixed, + size: 256, + precision: 64, }, ), }, @@ -239,7 +243,7 @@ func TestTypeFromStringValid(t *testing.T) { testType: "static array", expected: MakeStaticArrayType( MakeDynamicArrayType( - Type{typeFromEnum: Uint, typeSize: uint16(64)}, + Type{enumIndex: Uint, size: uint16(64)}, ), uint16(200), ), @@ -248,93 +252,111 @@ func TestTypeFromStringValid(t *testing.T) { { input: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", testType: "tuple type", - expected: MakeTupleType( - []Type{ + expected: Type{ + enumIndex: Tuple, + childTypes: []Type{ { - typeFromEnum: Uint, - typeSize: uint16(32), + enumIndex: Uint, + size: uint16(32), }, - MakeTupleType( - []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: uint16(256), - typePrecision: uint16(10), + enumIndex: Ufixed, + size: uint16(256), + precision: uint16(10), }, ), }, - ), + staticLength: 4, + }, MakeDynamicArrayType(MakeByteType()), }, - ), + staticLength: 3, + }, }, { input: "(uint32,(address,byte,bool[10],(ufixed256x10[])))", testType: "tuple type", - expected: MakeTupleType( - []Type{ + expected: Type{ + enumIndex: Tuple, + childTypes: []Type{ { - typeFromEnum: Uint, - typeSize: uint16(32), + enumIndex: Uint, + size: uint16(32), }, - MakeTupleType( - []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), - MakeTupleType( - []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ MakeDynamicArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: uint16(256), - typePrecision: uint16(10), + enumIndex: Ufixed, + size: uint16(256), + precision: uint16(10), }, ), }, - ), + staticLength: 1, + }, }, - ), + staticLength: 4, + }, }, - ), + staticLength: 2, + }, }, { input: "((uint32),(address,(byte,bool[10],ufixed256x10[])))", testType: "tuple type", - expected: MakeTupleType( - []Type{ - MakeTupleType( - []Type{ + expected: Type{ + enumIndex: Tuple, + childTypes: []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ { - typeFromEnum: Uint, - typeSize: uint16(32), + enumIndex: Uint, + size: uint16(32), }, }, - ), - MakeTupleType( - []Type{ + staticLength: 1, + }, + { + enumIndex: Tuple, + childTypes: []Type{ MakeAddressType(), - MakeTupleType( - []Type{ + { + enumIndex: Tuple, + childTypes: []Type{ MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - typeFromEnum: Ufixed, - typeSize: uint16(256), - typePrecision: uint16(10), + enumIndex: Ufixed, + size: uint16(256), + precision: uint16(10), }, ), }, - ), + staticLength: 3, + }, }, - ), + staticLength: 2, + }, }, - ), + staticLength: 2, + }, }, } for _, testcase := range testcases { @@ -434,7 +456,7 @@ func generateTupleType(baseTypes []Type, tupleTypes []Type) Type { resultTypes[i] = baseTypes[rand.Intn(len(baseTypes))] } } - return MakeTupleType(resultTypes) + return Type{enumIndex: Tuple, childTypes: resultTypes, staticLength: uint16(tupleLen)} } func TestTypeMISC(t *testing.T) { @@ -454,7 +476,7 @@ func TestTypeMISC(t *testing.T) { } for i := 8; i <= 512; i += 8 { for j := 1; j <= 160; j++ { - ufixedT, err := MakeUFixedType(uint16(i), uint16(j)) + ufixedT, err := MakeUfixedType(uint16(i), uint16(j)) require.NoError(t, err, "make ufixed type error") testpool = append(testpool, ufixedT) } @@ -532,7 +554,7 @@ func TestTypeMISC(t *testing.T) { require.Error(t, err, "byteLen test error on %s dynamic type, should have error", testType.String()) } else { - if testType.typeFromEnum == Tuple { + if testType.enumIndex == Tuple { sizeSum := 0 for _, childT := range testType.childTypes { childSize, err := childT.ByteLen() @@ -541,7 +563,7 @@ func TestTypeMISC(t *testing.T) { } require.Equal(t, sizeSum, byteLen, "%s do not match calculated byte length %d", testType.String(), sizeSum) - } else if testType.typeFromEnum == ArrayStatic { + } else if testType.enumIndex == ArrayStatic { childSize, err := testType.childTypes[0].ByteLen() require.NoError(t, err, "%s should not return error", testType.childTypes[0].String()) expected := childSize * int(testType.staticLength) From 3f6d73893755cd9fa5e8726d6032d8d172522b18 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 12:39:46 -0400 Subject: [PATCH 06/29] move from cmd/goal to data, resolve review --- {cmd/goal => data}/abi/abi.go | 113 +++++++++++----------- {cmd/goal => data}/abi/abi_encode_test.go | 0 {cmd/goal => data}/abi/abi_type_test.go | 0 3 files changed, 59 insertions(+), 54 deletions(-) rename {cmd/goal => data}/abi/abi.go (92%) rename {cmd/goal => data}/abi/abi_encode_test.go (100%) rename {cmd/goal => data}/abi/abi_type_test.go (100%) diff --git a/cmd/goal/abi/abi.go b/data/abi/abi.go similarity index 92% rename from cmd/goal/abi/abi.go rename to data/abi/abi.go index bc56b96765..686c7f9931 100644 --- a/cmd/goal/abi/abi.go +++ b/data/abi/abi.go @@ -242,8 +242,13 @@ func parseTupleContent(str string) ([]string, error) { } } + // check if the number of empty block placeholder is equal to number of sub-tuples + // if number do not match, this might be incurred by head/tail commas + // e.g. (,uint64,(bool,bool)) => ["", uint64, ""], with sub-tuple ["(bool,bool)"] if len(emptyStrIndex) != len(parenSegmentRecord) { - return []string{}, fmt.Errorf("head tail comma is not allowed") + return []string{}, + fmt.Errorf("parsing error: cannot replace tuple segment back: " + + "number of empty placeholder unmatch with sub-tuple number") } // replace back the tuple-formed type str @@ -440,7 +445,7 @@ func (t Type) ByteLen() (int, error) { // Value struct is the ABI Value, holding ABI Type information and the ABI value representation. type Value struct { - abiType Type + ABIType Type value interface{} } @@ -450,7 +455,7 @@ func (v Value) arrayToTuple() (Value, error) { var childT []Type var valueArr []Value - switch v.abiType.enumIndex { + switch v.ABIType.enumIndex { case String: strValue, err := v.GetString() if err != nil { @@ -473,16 +478,16 @@ func (v Value) arrayToTuple() (Value, error) { 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] + 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] + childT[i] = v.ABIType.childTypes[0] } valueArr = arrayElems default: @@ -495,14 +500,14 @@ func (v Value) arrayToTuple() (Value, error) { } return Value{ - abiType: castedTupleType, + 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.enumIndex { + switch v.ABIType.enumIndex { case Uint: bigIntValue, err := v.GetUint() if err != nil { @@ -510,7 +515,7 @@ func (v Value) Encode() ([]byte, error) { } // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use fillbytes bigIntBytes := bigIntValue.Bytes() - buffer := make([]byte, v.abiType.size/8-uint16(len(bigIntBytes))) + buffer := make([]byte, v.ABIType.size/8-uint16(len(bigIntBytes))) buffer = append(buffer, bigIntBytes...) return buffer, nil case Ufixed: @@ -520,7 +525,7 @@ func (v Value) Encode() ([]byte, error) { } // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use fillbytes encodeBuffer := ufixedValue.Bytes() - buffer := make([]byte, v.abiType.size/8-uint16(len(encodeBuffer))) + buffer := make([]byte, v.ABIType.size/8-uint16(len(encodeBuffer))) buffer = append(buffer, encodeBuffer...) return buffer, nil case Bool: @@ -549,7 +554,7 @@ func (v Value) Encode() ([]byte, error) { if err != nil { return []byte{}, err } - length := len(convertedTuple.abiType.childTypes) + length := len(convertedTuple.ABIType.childTypes) lengthEncode := make([]byte, 2) binary.BigEndian.PutUint16(lengthEncode, uint16(length)) @@ -595,7 +600,7 @@ func compressMultipleBool(valueList []Value) (uint8, error) { return 0, fmt.Errorf("value list passed in should be less than length 8") } for i := 0; i < len(valueList); i++ { - if valueList[i].abiType.enumIndex != Bool { + if valueList[i].ABIType.enumIndex != Bool { return 0, fmt.Errorf("bool type not matching in compressMultipleBool") } boolVal, err := valueList[i].GetBool() @@ -611,20 +616,20 @@ func compressMultipleBool(valueList []Value) (uint8, error) { // tupleEncoding encodes an ABI value of tuple type into an ABI encoded byte string. func tupleEncoding(v Value) ([]byte, error) { - if v.abiType.enumIndex != Tuple { + if v.ABIType.enumIndex != Tuple { return []byte{}, fmt.Errorf("type not supported in tupleEncoding") } - if len(v.abiType.childTypes) >= (1 << 16) { + 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) { + if len(tupleElems) != len(v.ABIType.childTypes) { return []byte{}, fmt.Errorf("tuple abi child type number unmatch with tuple argument number") } - heads, tails := make([][]byte, len(v.abiType.childTypes)), make([][]byte, len(v.abiType.childTypes)) + heads, tails := make([][]byte, len(v.ABIType.childTypes)), 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() { + for i := 0; i < len(v.ABIType.childTypes); i++ { + if tupleElems[i].ABIType.IsDynamic() { headsPlaceholder := []byte{0x00, 0x00} heads[i] = headsPlaceholder isDynamicIndex[i] = true @@ -634,11 +639,11 @@ func tupleEncoding(v Value) ([]byte, error) { } tails[i] = tailEncoding } else { - if tupleElems[i].abiType.enumIndex == Bool { + if tupleElems[i].ABIType.enumIndex == Bool { // search previous bool - before := findBoolLR(v.abiType.childTypes, i, -1) + before := findBoolLR(v.ABIType.childTypes, i, -1) // search after bool - after := findBoolLR(v.abiType.childTypes, i, 1) + 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") @@ -682,7 +687,7 @@ func tupleEncoding(v Value) ([]byte, error) { } head, tail := make([]byte, 0), make([]byte, 0) - for i := 0; i < len(v.abiType.childTypes); i++ { + for i := 0; i < len(v.ABIType.childTypes); i++ { head = append(head, heads[i]...) tail = append(tail, tails[i]...) } @@ -733,7 +738,7 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { if err != nil { return Value{}, err } - tupleDecoded.abiType = valueType + tupleDecoded.ABIType = valueType return tupleDecoded, nil case Address: if len(valueByte) != 32 { @@ -759,7 +764,7 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { if err != nil { return Value{}, err } - tupleDecoded.abiType = valueType + tupleDecoded.ABIType = valueType return tupleDecoded, nil case String: if len(valueByte) < 2 { @@ -878,7 +883,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { values = append(values, valueTi) } return Value{ - abiType: valueType, + ABIType: valueType, value: values, }, nil } @@ -919,7 +924,7 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { return Value{}, fmt.Errorf("passed value larger than uint size %d", size) } return Value{ - abiType: typeUint, + ABIType: typeUint, value: value, }, nil } @@ -935,14 +940,14 @@ func MakeUfixed(value *big.Int, size uint16, precision uint16) (Value, error) { if err != nil { return Value{}, err } - uintVal.abiType = ufixedValueType + 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(), + ABIType: MakeStringType(), value: value, } } @@ -950,7 +955,7 @@ func MakeString(value string) Value { // MakeByte takes a byte and returns an ABI Byte type value. func MakeByte(value byte) Value { return Value{ - abiType: MakeByteType(), + ABIType: MakeByteType(), value: value, } } @@ -958,7 +963,7 @@ func MakeByte(value byte) Value { // MakeAddress takes an [32]byte array and returns an ABI Address type value. func MakeAddress(value [32]byte) Value { return Value{ - abiType: MakeAddressType(), + ABIType: MakeAddressType(), value: value, } } @@ -971,13 +976,13 @@ func MakeDynamicArray(values []Value) (Value, error) { return Value{}, fmt.Errorf("dynamic array make error: 0 argument passed in") } for i := 0; i < len(values); i++ { - if !values[i].abiType.Equal(values[0].abiType) { + 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()) + values[i].ABIType.String(), values[0].ABIType.String()) } } return Value{ - abiType: MakeDynamicArrayType(values[0].abiType), + ABIType: MakeDynamicArrayType(values[0].ABIType), value: values, }, nil } @@ -990,13 +995,13 @@ func MakeStaticArray(values []Value) (Value, error) { return Value{}, fmt.Errorf("static array make error: 0 argument passed in") } for i := 0; i < len(values); i++ { - if !values[i].abiType.Equal(values[0].abiType) { + 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()) + values[i].ABIType.String(), values[0].ABIType.String()) } } return Value{ - abiType: MakeStaticArrayType(values[0].abiType, uint16(len(values))), + ABIType: MakeStaticArrayType(values[0].ABIType, uint16(len(values))), value: values, }, nil } @@ -1010,7 +1015,7 @@ func MakeTuple(values []Value) (Value, error) { } tupleType := make([]Type, len(values)) for i := 0; i < len(values); i++ { - tupleType[i] = values[i].abiType + tupleType[i] = values[i].ABIType } castedTupleType, err := MakeTupleType(tupleType) @@ -1019,7 +1024,7 @@ func MakeTuple(values []Value) (Value, error) { } return Value{ - abiType: castedTupleType, + ABIType: castedTupleType, value: values, }, nil } @@ -1027,14 +1032,14 @@ func MakeTuple(values []Value) (Value, error) { // MakeBool takes a boolean value and returns an ABI bool value. func MakeBool(value bool) Value { return Value{ - abiType: MakeBoolType(), + ABIType: MakeBoolType(), value: value, } } // GetUint8 tries to retreve an uint8 from an ABI Value. func (v Value) GetUint8() (uint8, error) { - if v.abiType.enumIndex != Uint || v.abiType.size > 8 { + if v.ABIType.enumIndex != Uint || v.ABIType.size > 8 { return 0, fmt.Errorf("value type unmatch or size too large") } bigIntForm, err := v.GetUint() @@ -1046,7 +1051,7 @@ func (v Value) GetUint8() (uint8, error) { // GetUint16 tries to retrieve an uint16 from an ABI Value. func (v Value) GetUint16() (uint16, error) { - if v.abiType.enumIndex != Uint || v.abiType.size > 16 { + if v.ABIType.enumIndex != Uint || v.ABIType.size > 16 { return 0, fmt.Errorf("value type unmatch or size too large") } bigIntForm, err := v.GetUint() @@ -1058,7 +1063,7 @@ func (v Value) GetUint16() (uint16, error) { // GetUint32 tries to retrieve an uint32 from an ABI Value. func (v Value) GetUint32() (uint32, error) { - if v.abiType.enumIndex != Uint || v.abiType.size > 32 { + if v.ABIType.enumIndex != Uint || v.ABIType.size > 32 { return 0, fmt.Errorf("value type unmatch or size too large") } bigIntForm, err := v.GetUint() @@ -1070,7 +1075,7 @@ func (v Value) GetUint32() (uint32, error) { // GetUint64 tries to retrieve an uint64 from an ABI Value. func (v Value) GetUint64() (uint64, error) { - if v.abiType.enumIndex != Uint || v.abiType.size > 64 { + if v.ABIType.enumIndex != Uint || v.ABIType.size > 64 { return 0, fmt.Errorf("value type unmatch or size too large") } bigIntForm, err := v.GetUint() @@ -1082,11 +1087,11 @@ func (v Value) GetUint64() (uint64, error) { // GetUint tries to retrieve an big uint from an ABI Value. func (v Value) GetUint() (*big.Int, error) { - if v.abiType.enumIndex != Uint { + if v.ABIType.enumIndex != Uint { return nil, fmt.Errorf("value type unmatch") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.abiType.size)) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) if sizeThreshold.Cmp(bigIntForm) <= 0 { return nil, fmt.Errorf("value is larger than uint size") } @@ -1095,11 +1100,11 @@ func (v Value) GetUint() (*big.Int, error) { // GetUfixed tries to retrieve an big rational number from an ABI Value. func (v Value) GetUfixed() (*big.Int, error) { - if v.abiType.enumIndex != Ufixed { + if v.ABIType.enumIndex != Ufixed { return nil, fmt.Errorf("value type unmatch, should be ufixed") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.abiType.size)) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) if sizeThreshold.Cmp(bigIntForm) <= 0 { return nil, fmt.Errorf("value is larger than ufixed size") } @@ -1108,7 +1113,7 @@ func (v Value) GetUfixed() (*big.Int, error) { // GetString tries to retrieve a string from ABI Value. func (v Value) GetString() (string, error) { - if v.abiType.enumIndex != String { + if v.ABIType.enumIndex != String { return "", fmt.Errorf("value type unmatch, should be ufixed") } stringForm := v.value.(string) @@ -1117,7 +1122,7 @@ func (v Value) GetString() (string, error) { // GetByte tries to retrieve a byte from ABI Value. func (v Value) GetByte() (byte, error) { - if v.abiType.enumIndex != Byte { + if v.ABIType.enumIndex != Byte { return byte(0), fmt.Errorf("value type unmatch, should be bytes") } bytesForm := v.value.(byte) @@ -1126,7 +1131,7 @@ func (v Value) GetByte() (byte, error) { // GetAddress tries to retrieve a [32]byte array from ABI Value. func (v Value) GetAddress() ([32]byte, error) { - if v.abiType.enumIndex != Address { + if v.ABIType.enumIndex != Address { return [32]byte{}, fmt.Errorf("value type unmatch, should be address") } addressForm := v.value.([32]byte) @@ -1135,7 +1140,7 @@ func (v Value) GetAddress() ([32]byte, error) { // GetValueByIndex retrieve value element by the index passed in func (v Value) GetValueByIndex(index uint16) (Value, error) { - switch v.abiType.enumIndex { + switch v.ABIType.enumIndex { case ArrayDynamic: elements := v.value.([]Value) if len(elements) <= int(index) { @@ -1144,7 +1149,7 @@ func (v Value) GetValueByIndex(index uint16) (Value, error) { return elements[index], nil case ArrayStatic, Tuple: elements := v.value.([]Value) - if v.abiType.staticLength <= index { + if v.ABIType.staticLength <= index { return Value{}, fmt.Errorf("cannot get element: index out of scope") } return elements[index], nil @@ -1155,7 +1160,7 @@ func (v Value) GetValueByIndex(index uint16) (Value, error) { // GetBool tries to retrieve a boolean value from the ABI Value. func (v Value) GetBool() (bool, error) { - if v.abiType.enumIndex != Bool { + if v.ABIType.enumIndex != Bool { return false, fmt.Errorf("value type unmatch, should be bool") } boolForm := v.value.(bool) diff --git a/cmd/goal/abi/abi_encode_test.go b/data/abi/abi_encode_test.go similarity index 100% rename from cmd/goal/abi/abi_encode_test.go rename to data/abi/abi_encode_test.go diff --git a/cmd/goal/abi/abi_type_test.go b/data/abi/abi_type_test.go similarity index 100% rename from cmd/goal/abi/abi_type_test.go rename to data/abi/abi_type_test.go From badcc28f19d6625ea8cb06459661fa31b58db412 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 14:08:01 -0400 Subject: [PATCH 07/29] rewrite is-dynamic, dynamic array --- data/abi/abi.go | 27 ++++++++++------------- data/abi/abi_encode_test.go | 4 ++-- data/abi/abi_type_test.go | 44 +++++++++++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/data/abi/abi.go b/data/abi/abi.go index 686c7f9931..38004b00ad 100644 --- a/data/abi/abi.go +++ b/data/abi/abi.go @@ -372,18 +372,14 @@ func (t Type) Equal(t0 Type) bool { // IsDynamic method decides if an ABI type is dynamic or static. func (t Type) IsDynamic() bool { switch t.enumIndex { - case ArrayStatic: - return t.childTypes[0].IsDynamic() case ArrayDynamic, String: return true - case Tuple: + default: for _, childT := range t.childTypes { if childT.IsDynamic() { return true } } - fallthrough - default: return false } } @@ -406,12 +402,13 @@ func (t Type) ByteLen() (int, error) { byteLen++ } return byteLen, nil + } else { + elemByteLen, err := t.childTypes[0].ByteLen() + if err != nil { + return -1, err + } + return int(t.staticLength) * elemByteLen, 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++ { @@ -969,20 +966,18 @@ func MakeAddress(value [32]byte) Value { } // MakeDynamicArray takes an array of ABI value and returns an ABI dynamic length array value. -func MakeDynamicArray(values []Value) (Value, error) { +func MakeDynamicArray(values []Value, elemType Type) (Value, error) { if len(values) >= (1 << 16) { return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") - } else if len(values) == 0 { - return Value{}, fmt.Errorf("dynamic array make error: 0 argument passed in") } for i := 0; i < len(values); i++ { - if !values[i].ABIType.Equal(values[0].ABIType) { + if !values[i].ABIType.Equal(elemType) { return Value{}, fmt.Errorf("type mismatch: %s and %s", - values[i].ABIType.String(), values[0].ABIType.String()) + values[i].ABIType.String(), elemType.String()) } } return Value{ - ABIType: MakeDynamicArrayType(values[0].ABIType), + ABIType: MakeDynamicArrayType(elemType), value: values, }, nil } diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index b4b5df3716..d3f888f56d 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -179,7 +179,7 @@ func TestEncodeValid(t *testing.T) { expected := []byte{ 0x00, 0x0A, 0b01010101, 0b01000000, } - boolArr, err := MakeDynamicArray(arrayElems) + 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") @@ -362,7 +362,7 @@ func TestDecodeValid(t *testing.T) { for index, bVal := range inputBase { arrayElems[index] = MakeBool(bVal) } - expected, err := MakeDynamicArray(arrayElems) + expected, err := MakeDynamicArray(arrayElems, MakeBoolType()) require.NoError(t, err, "make expected value should not return error") inputEncoded := []byte{ 0x00, 0x0A, 0b01010101, 0b01000000, diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 463f5af930..c5e8f60c99 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -556,19 +556,45 @@ func TestTypeMISC(t *testing.T) { } else { if testType.enumIndex == Tuple { sizeSum := 0 - for _, childT := range testType.childTypes { - childSize, err := childT.ByteLen() - require.NoError(t, err, "valid tuple child type should not return error: %s", childT.String()) - sizeSum += childSize + for i := 0; i < len(testType.childTypes); i++ { + if testType.childTypes[i].enumIndex == 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.enumIndex == ArrayStatic { - childSize, err := testType.childTypes[0].ByteLen() - require.NoError(t, err, "%s should not return error", 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) + if testType.childTypes[0].enumIndex == 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, 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++ From a613273c069cd7d41b9a5cd123f381cd417967c9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 15:37:30 -0400 Subject: [PATCH 08/29] update dynamic array 0 element support, test needed --- data/abi/abi.go | 25 ++++++++++--------------- data/abi/abi_type_test.go | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/data/abi/abi.go b/data/abi/abi.go index 38004b00ad..dbb3fe99aa 100644 --- a/data/abi/abi.go +++ b/data/abi/abi.go @@ -338,8 +338,6 @@ func MakeStringType() Type { func MakeTupleType(argumentTypes []Type) (Type, error) { if len(argumentTypes) >= (1<<16) || len(argumentTypes) == 0 { return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") - } else if len(argumentTypes) == 0 { - return Type{}, fmt.Errorf("tuple type child type 0 error") } return Type{ enumIndex: Tuple, @@ -402,13 +400,12 @@ func (t Type) ByteLen() (int, error) { byteLen++ } return byteLen, nil - } else { - elemByteLen, err := t.childTypes[0].ByteLen() - if err != nil { - return -1, err - } - return int(t.staticLength) * elemByteLen, 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++ { @@ -792,7 +789,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } dynamicIndex := binary.BigEndian.Uint16(valueBytes[iterIndex : iterIndex+2]) if len(dynamicSegments) > 0 { - dynamicSegments[len(dynamicSegments)-1].right = int(dynamicIndex) - 1 + dynamicSegments[len(dynamicSegments)-1].right = int(dynamicIndex) } dynamicSegments = append(dynamicSegments, segment{ left: int(dynamicIndex), @@ -838,7 +835,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } } if len(dynamicSegments) > 0 { - dynamicSegments[len(dynamicSegments)-1].right = len(valueBytes) - 1 + dynamicSegments[len(dynamicSegments)-1].right = len(valueBytes) iterIndex = len(valueBytes) } if iterIndex < len(valueBytes) { @@ -853,11 +850,11 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } for i := 0; i < len(segIndexArr); i++ { if i%2 == 1 { - if i != len(segIndexArr)-1 && segIndexArr[i]+1 != segIndexArr[i+1] { + if i != len(segIndexArr)-1 && segIndexArr[i] != segIndexArr[i+1] { return Value{}, fmt.Errorf("dynamic segment should sit next to each other") } } else { - if segIndexArr[i] >= segIndexArr[i+1] { + if segIndexArr[i] > segIndexArr[i+1] { return Value{}, fmt.Errorf("dynamic segment should display a [l, r] space") } } @@ -866,7 +863,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { segIndex := 0 for i := 0; i < len(valueType.childTypes); i++ { if valuePartition[i] == nil { - valuePartition[i] = valueBytes[dynamicSegments[segIndex].left : dynamicSegments[segIndex].right+1] + valuePartition[i] = valueBytes[dynamicSegments[segIndex].left : dynamicSegments[segIndex].right] segIndex++ } } @@ -1005,8 +1002,6 @@ func MakeStaticArray(values []Value) (Value, error) { func MakeTuple(values []Value) (Value, error) { if len(values) >= (1 << 16) { return Value{}, fmt.Errorf("tuple make error: pass in argument number larger than 2^16") - } else if len(values) == 0 { - return Value{}, fmt.Errorf("tuple make error: 0 argument passed in") } tupleType := make([]Type, len(values)) for i := 0; i < len(values); i++ { diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index c5e8f60c99..1ce50e318a 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -425,7 +425,6 @@ func TestTypeFromStringInvalid(t *testing.T) { "(,uint128,byte[])", "(address,ufixed64x5,)", "(byte[16],somethingwrong)", - "()", "( )", "((uint32)", "(byte,,byte)", From bf4e035ae6386bfdc3d633d5b0d2e680889f2508 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 15:40:29 -0400 Subject: [PATCH 09/29] minor --- data/abi/abi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/abi/abi.go b/data/abi/abi.go index dbb3fe99aa..0f4349f981 100644 --- a/data/abi/abi.go +++ b/data/abi/abi.go @@ -863,7 +863,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { segIndex := 0 for i := 0; i < len(valueType.childTypes); i++ { if valuePartition[i] == nil { - valuePartition[i] = valueBytes[dynamicSegments[segIndex].left : dynamicSegments[segIndex].right] + valuePartition[i] = valueBytes[dynamicSegments[segIndex].left:dynamicSegments[segIndex].right] segIndex++ } } From 1e8f62b92d13e87f30847ca831ef89f26014890e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 30 Aug 2021 16:17:43 -0400 Subject: [PATCH 10/29] minor --- data/abi/abi_type_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 1ce50e318a..996fd8f678 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -585,7 +585,7 @@ func TestTypeMISC(t *testing.T) { } actual, err := testType.ByteLen() require.NoError(t, err, "%s should not return error on byteLen test") - require.Equal(t, expected, actual, "%s do not match calculated byte length %d", + require.Equal(t, int(expected), actual, "%s do not match calculated byte length %d", testType.String(), expected) } else { childSize, err := testType.childTypes[0].ByteLen() From c570d806b99bba564c47e4d4a550d92ab2d309ca Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 Aug 2021 12:48:42 -0400 Subject: [PATCH 11/29] minor --- data/abi/abi.go | 50 +++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/data/abi/abi.go b/data/abi/abi.go index 0f4349f981..358a926141 100644 --- a/data/abi/abi.go +++ b/data/abi/abi.go @@ -248,7 +248,7 @@ func parseTupleContent(str string) ([]string, error) { if len(emptyStrIndex) != len(parenSegmentRecord) { return []string{}, fmt.Errorf("parsing error: cannot replace tuple segment back: " + - "number of empty placeholder unmatch with sub-tuple number") + "number of empty placeholders do not match with number of sub-tuples") } // replace back the tuple-formed type str @@ -336,7 +336,7 @@ func MakeStringType() Type { // MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument. func MakeTupleType(argumentTypes []Type) (Type, error) { - if len(argumentTypes) >= (1<<16) || len(argumentTypes) == 0 { + if len(argumentTypes) >= (1 << 16) { return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") } return Type{ @@ -456,7 +456,10 @@ func (v Value) arrayToTuple() (Value, error) { return Value{}, err } strByte := []byte(strValue) - childT, valueArr = make([]Type, len(strByte)), make([]Value, len(strByte)) + + 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]) @@ -466,7 +469,10 @@ func (v Value) arrayToTuple() (Value, error) { if err != nil { return Value{}, err } - childT, valueArr = make([]Type, 32), make([]Value, 32) + + childT = make([]Type, 32) + valueArr = make([]Value, 32) + for i := 0; i < 32; i++ { childT[i] = MakeByteType() valueArr[i] = MakeByte(addr[i]) @@ -620,8 +626,11 @@ func tupleEncoding(v Value) ([]byte, error) { if len(tupleElems) != len(v.ABIType.childTypes) { return []byte{}, fmt.Errorf("tuple abi child type number unmatch with tuple argument number") } - heads, tails := make([][]byte, len(v.ABIType.childTypes)), make([][]byte, len(v.ABIType.childTypes)) + + 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() { headsPlaceholder := []byte{0x00, 0x00} @@ -780,12 +789,14 @@ func Decode(valueByte []byte, valueType Type) (Value, 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, valuePartition := make([]segment, 0), make([][]byte, 0) + 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:]) < 2 { - return Value{}, fmt.Errorf("ill formed tuple encoding") + return Value{}, fmt.Errorf("ill formed tuple dynamic typed value encoding") } dynamicIndex := binary.BigEndian.Uint16(valueBytes[iterIndex : iterIndex+2]) if len(dynamicSegments) > 0 { @@ -819,6 +830,8 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } i += after iterIndex++ + } else { + return Value{}, fmt.Errorf("expected before bool number mod 8 == 0") } } else { // not bool ... @@ -843,26 +856,18 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } // check segment indices are valid - segIndexArr := make([]int, len(dynamicSegments)*2) for index, seg := range dynamicSegments { - segIndexArr[index*2] = seg.left - segIndexArr[index*2+1] = seg.right - } - for i := 0; i < len(segIndexArr); i++ { - if i%2 == 1 { - if i != len(segIndexArr)-1 && segIndexArr[i] != segIndexArr[i+1] { - return Value{}, fmt.Errorf("dynamic segment should sit next to each other") - } - } else { - if segIndexArr[i] > segIndexArr[i+1] { - return Value{}, fmt.Errorf("dynamic segment should display a [l, r] space") - } + 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 valuePartition[i] == nil { + if valueType.childTypes[i].IsDynamic() { valuePartition[i] = valueBytes[dynamicSegments[segIndex].left:dynamicSegments[segIndex].right] segIndex++ } @@ -962,7 +967,8 @@ func MakeAddress(value [32]byte) Value { } } -// MakeDynamicArray takes an array of ABI value and returns an ABI dynamic length array 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) >= (1 << 16) { return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") From 500ad45b5edd9537c18fa70f8350c3d8df51ab5b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 31 Aug 2021 15:58:25 -0400 Subject: [PATCH 12/29] add more testcase for abi encode/decode --- data/abi/abi_encode_test.go | 176 ++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index d3f888f56d..20615feb9d 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -27,6 +27,8 @@ import ( "github.com/stretchr/testify/require" ) +// TODO need testcase (bool[2], bool[2]), empty dynamic array + func TestEncodeValid(t *testing.T) { partitiontest.PartitionTest(t) for intSize := 8; intSize <= 512; intSize += 8 { @@ -211,6 +213,70 @@ func TestEncodeValid(t *testing.T) { require.NoError(t, err, "string tuple encoding should not return error") require.Equal(t, expected, stringTupleEncode, "string tuple encoding not match expected") }) + + 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") + }) + + 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") + }) + + 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") + }) + + 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 := []byte{} + 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) { @@ -408,6 +474,116 @@ func TestDecodeValid(t *testing.T) { require.NoError(t, err, "decoding dynamic tuple should not return error") require.Equal(t, expected, actual, "dynamic tuple not match with expected") }) + + 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayStatic, + staticLength: 2, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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") + }) + + 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayStatic, + staticLength: 2, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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") + }) + + 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayDynamic, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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") + }) + + 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 := []byte{} + decoded, err := Decode(encodedInput, Type{ + enumIndex: 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) { From 7d563f19556ae8f7d4fb63ebf59323cf8adec259 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Sep 2021 13:51:16 -0400 Subject: [PATCH 13/29] update comments in encoding test --- data/abi/abi_encode_test.go | 384 +++++++++++++++++++++++++++++++++--- 1 file changed, 355 insertions(+), 29 deletions(-) diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index 20615feb9d..801adeb74d 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -27,23 +27,28 @@ import ( "github.com/stretchr/testify/require" ) -// TODO need testcase (bool[2], bool[2]), empty dynamic array - 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 + // also check if uint can contain max uint value (2^size - 1) under specific byte size 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") - valueUint, err := MakeUint(randomInt, uint16(intSize)) + + 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") - encodedUint, err := valueUint.Encode() + uintBytesActual, err := uintValue.Encode() + require.NoError(t, err, "uint encode fail") - randomIntByte := randomInt.Bytes() - buffer := make([]byte, intSize/8-len(randomIntByte)) - buffer = append(buffer, randomIntByte...) - require.Equal(t, buffer, encodedUint, "encode uint not match with expected") + require.Equal(t, expected, uintBytesActual, "encode uint not match with expected") } // 2^[size] - 1 test largest := big.NewInt(0).Add( @@ -57,6 +62,9 @@ func TestEncodeValid(t *testing.T) { require.Equal(t, largest.Bytes(), encoded, "encode uint largest do not match with expected") } + // encoding test for ufixed, iterating through all the valid ufixed size 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^size - 1) under specific byte size for size := 8; size <= 512; size += 8 { upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) largest := big.NewInt(0).Add( @@ -89,24 +97,27 @@ func TestEncodeValid(t *testing.T) { } } + // 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") - addressBytes := randomAddrInt.Bytes() - address := make([]byte, 32-len(addressBytes)) - address = append(address, addressBytes...) + rand256Bytes := randomAddrInt.Bytes() + addrBytesExpected := make([]byte, 32-len(rand256Bytes)) + addrBytesExpected = append(addrBytesExpected, rand256Bytes...) var addrBytes [32]byte - copy(addrBytes[:], address[:32]) + copy(addrBytes[:], addrBytesExpected[:32]) addressValue := MakeAddress(addrBytes) - addrEncode, err := addressValue.Encode() + addrBytesActual, err := addressValue.Encode() require.NoError(t, err, "address encode fail") - require.Equal(t, address, addrEncode, "encode addr not match with expected") + 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() @@ -118,6 +129,7 @@ func TestEncodeValid(t *testing.T) { 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() @@ -126,20 +138,30 @@ func TestEncodeValid(t *testing.T) { require.Equal(t, expected, byteEncode, "encode byte not match with expected") } - for length := 1; length <= 10; length++ { + // 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)) - head := make([]byte, 2) - binary.BigEndian.PutUint16(head, uint16(utf8ByteLen)) - expected := append(head, []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)) @@ -156,6 +178,8 @@ func TestEncodeValid(t *testing.T) { 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)) @@ -172,6 +196,8 @@ func TestEncodeValid(t *testing.T) { 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)) @@ -188,11 +214,25 @@ func TestEncodeValid(t *testing.T) { 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 { @@ -214,6 +254,14 @@ func TestEncodeValid(t *testing.T) { 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) @@ -233,6 +281,16 @@ func TestEncodeValid(t *testing.T) { 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) @@ -255,6 +313,16 @@ func TestEncodeValid(t *testing.T) { 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") @@ -269,10 +337,12 @@ func TestEncodeValid(t *testing.T) { 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 := []byte{} + 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") @@ -281,23 +351,30 @@ func TestEncodeValid(t *testing.T) { func TestDecodeValid(t *testing.T) { partitiontest.PartitionTest(t) + // decoding test for uint, iterating through all valid uint size + // randomly take 1000 tests on each valid size + // 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") - valueUint, err := MakeUint(randomInt, uint16(intSize)) + expected, err := MakeUint(randomInt, uint16(intSize)) require.NoError(t, err, "makeUint Fail") - encodedUint, err := valueUint.Encode() + 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") - decodedUint, err := Decode(encodedUint, uintType) + actual, err := Decode(encodedUint, uintType) require.NoError(t, err, "decoding uint should not return error") - require.Equal(t, valueUint, decodedUint, "decode uint fail to match expected value") + require.Equal(t, expected, actual, "decode uint fail to match expected value") } } + // decoding test for ufixed, iterating through all valid ufixed size 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++ { @@ -321,6 +398,9 @@ func TestDecodeValid(t *testing.T) { } } + // 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) @@ -342,6 +422,7 @@ func TestDecodeValid(t *testing.T) { 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() @@ -351,6 +432,7 @@ func TestDecodeValid(t *testing.T) { 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() @@ -360,7 +442,10 @@ func TestDecodeValid(t *testing.T) { require.Equal(t, byteValue, byteDecode, "decode byte not match with expected") } - for length := 1; length <= 10; length++ { + // 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) @@ -372,6 +457,9 @@ func TestDecodeValid(t *testing.T) { } } + // 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)) @@ -380,11 +468,17 @@ func TestDecodeValid(t *testing.T) { } 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)))) + 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)) @@ -403,6 +497,18 @@ func TestDecodeValid(t *testing.T) { 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)) @@ -422,10 +528,16 @@ func TestDecodeValid(t *testing.T) { 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) { - inputBase := []bool{false, true, false, true, false, true, false, true, false, true} - arrayElems := make([]Value, len(inputBase)) - for index, bVal := range inputBase { + 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()) @@ -438,6 +550,19 @@ func TestDecodeValid(t *testing.T) { 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, @@ -475,6 +600,14 @@ func TestDecodeValid(t *testing.T) { 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) @@ -509,6 +642,16 @@ func TestDecodeValid(t *testing.T) { 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) @@ -545,6 +688,16 @@ func TestDecodeValid(t *testing.T) { 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") @@ -572,10 +725,13 @@ func TestDecodeValid(t *testing.T) { 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 := []byte{} + encodedInput := make([]byte, 0) decoded, err := Decode(encodedInput, Type{ enumIndex: Tuple, staticLength: 0, @@ -588,6 +744,10 @@ func TestDecodeValid(t *testing.T) { 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) @@ -595,6 +755,10 @@ func TestDecodeInvalid(t *testing.T) { 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) @@ -602,6 +766,10 @@ func TestDecodeInvalid(t *testing.T) { 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, @@ -619,6 +787,10 @@ func TestDecodeInvalid(t *testing.T) { 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, @@ -637,6 +809,10 @@ func TestDecodeInvalid(t *testing.T) { 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, @@ -646,6 +822,10 @@ func TestDecodeInvalid(t *testing.T) { 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, @@ -655,6 +835,22 @@ func TestDecodeInvalid(t *testing.T) { 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, @@ -687,4 +883,134 @@ func TestDecodeInvalid(t *testing.T) { ) 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayStatic, + staticLength: 2, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayStatic, + staticLength: 2, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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{ + enumIndex: Tuple, + staticLength: 2, + childTypes: []Type{ + { + enumIndex: ArrayDynamic, + childTypes: []Type{MakeBoolType()}, + }, + { + enumIndex: 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{ + enumIndex: Tuple, + staticLength: 0, + childTypes: []Type{}, + }) + require.Error(t, err, "decode corrupted empty tuple should return error") + }) } From 2f3fe4230c12c0cecc97e844c88e2799a05acfee Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 1 Sep 2021 15:13:27 -0400 Subject: [PATCH 14/29] minor --- data/abi/abi_type_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 996fd8f678..7e8db52d52 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -28,8 +28,6 @@ import ( "github.com/stretchr/testify/require" ) -// TODO need a fuzz test for the parsing - func TestMakeTypeValid(t *testing.T) { partitiontest.PartitionTest(t) // uint @@ -503,7 +501,7 @@ func TestTypeMISC(t *testing.T) { } testpoolTuple := make([]Type, 0) - for i := 0; i < 1000; i++ { + for i := 0; i < 100; i++ { testpoolTuple = append(testpoolTuple, generateTupleType(testpool, testpoolTuple)) } for _, testcaseTuple := range testpoolTuple { @@ -511,7 +509,7 @@ func TestTypeMISC(t *testing.T) { } tupleTestCount := 0 - for tupleTestCount < 1000 { + for tupleTestCount < 100 { index0 := rand.Intn(len(testpoolTuple)) index1 := rand.Intn(len(testpoolTuple)) if testpoolTuple[index0].String() == testpoolTuple[index1].String() { @@ -525,7 +523,7 @@ func TestTypeMISC(t *testing.T) { testpool = append(testpool, testpoolTuple...) isDynamicCount := 0 - for isDynamicCount < 1000 { + for isDynamicCount < 100 { index := rand.Intn(len(testpool)) isDynamicArr := strings.Contains(testpool[index].String(), "[]") isDynamicStr := strings.Contains(testpool[index].String(), "string") @@ -545,7 +543,7 @@ func TestTypeMISC(t *testing.T) { require.Equal(t, 1, boolByteLen, "bool type bytelen should be 1") byteLenTestCount := 0 - for byteLenTestCount < 1000 { + for byteLenTestCount < 100 { index := rand.Intn(len(testpool)) testType := testpool[index] byteLen, err := testType.ByteLen() From c87719c4dcd19e42f92a6aca6c2be032cf121012 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 8 Sep 2021 09:50:07 -0400 Subject: [PATCH 15/29] attempt to split abi.go into 2 files --- data/abi/{abi.go => abi_encode.go} | 415 --------------------------- data/abi/abi_encode_test.go | 2 +- data/abi/abi_type.go | 436 +++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+), 416 deletions(-) rename data/abi/{abi.go => abi_encode.go} (64%) create mode 100644 data/abi/abi_type.go diff --git a/data/abi/abi.go b/data/abi/abi_encode.go similarity index 64% rename from data/abi/abi.go rename to data/abi/abi_encode.go index 358a926141..75fa6fe4a8 100644 --- a/data/abi/abi.go +++ b/data/abi/abi_encode.go @@ -20,423 +20,8 @@ import ( "encoding/binary" "fmt" "math/big" - "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 { - enumIndex BaseType - childTypes []Type - - // only can be applied to `uint` size or `ufixed` size - size 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.enumIndex { - case Uint: - return "uint" + strconv.Itoa(int(t.size)) - case Byte: - return "byte" - case Ufixed: - return "ufixed" + strconv.Itoa(int(t.size)) + "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("Bruh you should not be here") - } -} - -// 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 arrayArgType, err - } - return MakeDynamicArrayType(arrayArgType), nil - case strings.HasSuffix(str, "]"): - stringMatches := regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`).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] - 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 := regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`).FindStringSubmatch(str) - // match string itself, then type-size, 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. -func parseTupleContent(str string) ([]string, error) { - // argument str is the content between parentheses of tuple, i.e. - // (...... str ......) - // ^ ^ - parenSegmentRecord := make([]segment, 0) - stack := make([]int, 0) - // get the most exterior parentheses segment (not overlapped by other parentheses) - // illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"] - 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) - } - - // kudos to Jason Paulos - if strings.Contains(str, ",,") { - return []string{}, fmt.Errorf("no consecutive commas") - } - - // 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:] - } - - // maintain list of empty strings as placeholders for tuple-formed type str - tupleStrSegs := strings.Split(strCopied, ",") - emptyStrIndex := make([]int, 0) - for index, segStr := range tupleStrSegs { - if segStr == "" { - emptyStrIndex = append(emptyStrIndex, index) - } - } - - // check if the number of empty block placeholder is equal to number of sub-tuples - // if number do not match, this might be incurred by head/tail commas - // e.g. (,uint64,(bool,bool)) => ["", uint64, ""], with sub-tuple ["(bool,bool)"] - if len(emptyStrIndex) != len(parenSegmentRecord) { - return []string{}, - fmt.Errorf("parsing error: cannot replace tuple segment back: " + - "number of empty placeholders do not match with number of sub-tuples") - } - - // replace back the tuple-formed type str - for index, replaceIndex := range emptyStrIndex { - tupleStrSegs[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] - } - - return tupleStrSegs, nil -} - -// MakeUintType makes `Uint` ABI type by taking a type size argument. -// The range of type size is [8, 512] and type size % 8 == 0. -func MakeUintType(typeSize uint16) (Type, error) { - if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { - return Type{}, fmt.Errorf("unsupported uint type size: %d", typeSize) - } - return Type{ - enumIndex: Uint, - size: typeSize, - }, nil -} - -// MakeByteType makes `Byte` ABI type. -func MakeByteType() Type { - return Type{ - enumIndex: Byte, - } -} - -// MakeUfixedType makes `UFixed` ABI type by taking type size and type precision as arguments. -// The range of type size is [8, 512] and type size % 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 size: %d", typeSize) - } - if typePrecision > 160 || typePrecision < 1 { - return Type{}, fmt.Errorf("unsupported ufixed type precision: %d", typePrecision) - } - return Type{ - enumIndex: Ufixed, - size: typeSize, - precision: typePrecision, - }, nil -} - -// MakeBoolType makes `Bool` ABI type. -func MakeBoolType() Type { - return Type{ - enumIndex: 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{ - enumIndex: ArrayStatic, - childTypes: []Type{argumentType}, - staticLength: arrayLength, - } -} - -// MakeAddressType makes `Address` ABI type. -func MakeAddressType() Type { - return Type{ - enumIndex: Address, - } -} - -// MakeDynamicArrayType makes dynamic length array by taking array element type as argument. -func MakeDynamicArrayType(argumentType Type) Type { - return Type{ - enumIndex: ArrayDynamic, - childTypes: []Type{argumentType}, - } -} - -// MakeStringType makes `String` ABI type. -func MakeStringType() Type { - return Type{ - enumIndex: 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) >= (1 << 16) { - return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") - } - return Type{ - enumIndex: 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.enumIndex != t0.enumIndex { - return false - } else if t.precision != t0.precision || t.size != t0.size { - return false - } else if t.staticLength != t0.staticLength { - return false - } else { - 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.enumIndex { - case ArrayDynamic, String: - return true - default: - for _, childT := range t.childTypes { - if childT.IsDynamic() { - return true - } - } - return false - } -} - -// ByteLen method calculates the byte length of a static ABI type. -func (t Type) ByteLen() (int, error) { - switch t.enumIndex { - case Address: - return 32, nil - case Byte: - return 1, nil - case Uint, Ufixed: - return int(t.size / 8), nil - case Bool: - return 1, nil - case ArrayStatic: - if t.childTypes[0].enumIndex == 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].enumIndex == Bool { - // search previous bool - before := findBoolLR(t.childTypes, i, -1) - // search after bool - after := findBoolLR(t.childTypes, i, 1) - // append to heads and tails - if before%8 != 0 { - return -1, fmt.Errorf("expected before has number of bool mod 8 = 0") - } - if after > 7 { - after = 7 - } - i += after - 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()) - } -} - // Value struct is the ABI Value, holding ABI Type information and the ABI value representation. type Value struct { ABIType Type diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index 801adeb74d..f39944f93a 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -32,7 +32,6 @@ func TestEncodeValid(t *testing.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 - // also check if uint can contain max uint value (2^size - 1) under specific byte size for intSize := 8; intSize <= 512; intSize += 8 { upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize)) for i := 0; i < 1000; i++ { @@ -51,6 +50,7 @@ func TestEncodeValid(t *testing.T) { require.Equal(t, expected, uintBytesActual, "encode uint not match with expected") } // 2^[size] - 1 test + // check if uint can contain max uint value (2^size - 1) largest := big.NewInt(0).Add( upperLimit, big.NewInt(1).Neg(big.NewInt(1)), diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go new file mode 100644 index 0000000000..edf88a4cc6 --- /dev/null +++ b/data/abi/abi_type.go @@ -0,0 +1,436 @@ +// 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" + "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 { + enumIndex BaseType + childTypes []Type + + // only can be applied to `uint` size or `ufixed` size + size 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.enumIndex { + case Uint: + return "uint" + strconv.Itoa(int(t.size)) + case Byte: + return "byte" + case Ufixed: + return "ufixed" + strconv.Itoa(int(t.size)) + "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("Bruh you should not be here") + } +} + +// 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 arrayArgType, err + } + return MakeDynamicArrayType(arrayArgType), nil + case strings.HasSuffix(str, "]"): + stringMatches := regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`).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] + 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 := regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`).FindStringSubmatch(str) + // match string itself, then type-size, 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. +func parseTupleContent(str string) ([]string, error) { + // argument str is the content between parentheses of tuple, i.e. + // (...... str ......) + // ^ ^ + parenSegmentRecord := make([]segment, 0) + stack := make([]int, 0) + // get the most exterior parentheses segment (not overlapped by other parentheses) + // illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"] + 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) + } + + // kudos to Jason Paulos + if strings.Contains(str, ",,") { + return []string{}, fmt.Errorf("no consecutive commas") + } + + // 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:] + } + + // maintain list of empty strings as placeholders for tuple-formed type str + tupleStrSegs := strings.Split(strCopied, ",") + emptyStrIndex := make([]int, 0) + for index, segStr := range tupleStrSegs { + if segStr == "" { + emptyStrIndex = append(emptyStrIndex, index) + } + } + + // check if the number of empty block placeholder is equal to number of sub-tuples + // if number do not match, this might be incurred by head/tail commas + // e.g. (,uint64,(bool,bool)) => ["", uint64, ""], with sub-tuple ["(bool,bool)"] + if len(emptyStrIndex) != len(parenSegmentRecord) { + return []string{}, + fmt.Errorf("parsing error: cannot replace tuple segment back: " + + "number of empty placeholders do not match with number of sub-tuples") + } + + // replace back the tuple-formed type str + for index, replaceIndex := range emptyStrIndex { + tupleStrSegs[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] + } + + return tupleStrSegs, nil +} + +// MakeUintType makes `Uint` ABI type by taking a type size argument. +// The range of type size is [8, 512] and type size % 8 == 0. +func MakeUintType(typeSize uint16) (Type, error) { + if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { + return Type{}, fmt.Errorf("unsupported uint type size: %d", typeSize) + } + return Type{ + enumIndex: Uint, + size: typeSize, + }, nil +} + +// MakeByteType makes `Byte` ABI type. +func MakeByteType() Type { + return Type{ + enumIndex: Byte, + } +} + +// MakeUfixedType makes `UFixed` ABI type by taking type size and type precision as arguments. +// The range of type size is [8, 512] and type size % 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 size: %d", typeSize) + } + if typePrecision > 160 || typePrecision < 1 { + return Type{}, fmt.Errorf("unsupported ufixed type precision: %d", typePrecision) + } + return Type{ + enumIndex: Ufixed, + size: typeSize, + precision: typePrecision, + }, nil +} + +// MakeBoolType makes `Bool` ABI type. +func MakeBoolType() Type { + return Type{ + enumIndex: 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{ + enumIndex: ArrayStatic, + childTypes: []Type{argumentType}, + staticLength: arrayLength, + } +} + +// MakeAddressType makes `Address` ABI type. +func MakeAddressType() Type { + return Type{ + enumIndex: Address, + } +} + +// MakeDynamicArrayType makes dynamic length array by taking array element type as argument. +func MakeDynamicArrayType(argumentType Type) Type { + return Type{ + enumIndex: ArrayDynamic, + childTypes: []Type{argumentType}, + } +} + +// MakeStringType makes `String` ABI type. +func MakeStringType() Type { + return Type{ + enumIndex: 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) >= (1 << 16) { + return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") + } + return Type{ + enumIndex: 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.enumIndex != t0.enumIndex { + return false + } else if t.precision != t0.precision || t.size != t0.size { + return false + } else if t.staticLength != t0.staticLength { + return false + } else { + 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.enumIndex { + case ArrayDynamic, String: + return true + default: + for _, childT := range t.childTypes { + if childT.IsDynamic() { + return true + } + } + return false + } +} + +// ByteLen method calculates the byte length of a static ABI type. +func (t Type) ByteLen() (int, error) { + switch t.enumIndex { + case Address: + return 32, nil + case Byte: + return 1, nil + case Uint, Ufixed: + return int(t.size / 8), nil + case Bool: + return 1, nil + case ArrayStatic: + if t.childTypes[0].enumIndex == 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].enumIndex == Bool { + // search previous bool + before := findBoolLR(t.childTypes, i, -1) + // search after bool + after := findBoolLR(t.childTypes, i, 1) + // append to heads and tails + if before%8 != 0 { + return -1, fmt.Errorf("expected before has number of bool mod 8 = 0") + } + if after > 7 { + after = 7 + } + i += after + 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()) + } +} From 2dd602881bd76184ca5da981d51db6528a22d683 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 8 Sep 2021 10:54:26 -0400 Subject: [PATCH 16/29] separate abi files to smaller files --- data/abi/abi_encode.go | 305 ----------------------------------------- data/abi/abi_type.go | 22 +++ data/abi/abi_value.go | 304 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+), 305 deletions(-) create mode 100644 data/abi/abi_value.go diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index 75fa6fe4a8..e18280bcda 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -22,12 +22,6 @@ import ( "math/big" ) -// Value struct is the ABI Value, holding ABI Type information and the ABI value representation. -type Value struct { - ABIType Type - value interface{} -} - // 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) { @@ -155,29 +149,6 @@ func (v Value) Encode() ([]byte, error) { } } -// findBoolLR takes a list of type, the current index, and search direction (+1/-1). -// 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].enumIndex == Bool { - if curr != len(typeList)-1 && delta > 0 { - until++ - } else if curr > 0 && delta < 0 { - until++ - } else { - break - } - } else { - until-- - break - } - } - return until -} - // compressMultipleBool compress consecutive bool values into a byte in ABI tuple/array value. func compressMultipleBool(valueList []Value) (uint8, error) { var res uint8 = 0 @@ -471,279 +442,3 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { value: values, }, nil } - -// MakeUint8 takes a go `uint8` and gives an ABI Value of ABI type `uint8`. -func MakeUint8(value uint8) (Value, error) { - bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 8) -} - -// MakeUint16 takes a go `uint16` and gives an ABI Value of ABI type `uint16`. -func MakeUint16(value uint16) (Value, error) { - bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 16) -} - -// MakeUint32 takes a go `uint32` and gives an ABI Value of ABI type `uint32`. -func MakeUint32(value uint32) (Value, error) { - bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 32) -} - -// MakeUint64 takes a go `uint64` and gives an ABI Value of ABI type `uint64`. -func MakeUint64(value uint64) (Value, error) { - bigInt := big.NewInt(int64(0)).SetUint64(value) - return MakeUint(bigInt, 64) -} - -// MakeUint takes a big integer representation and a type size, -// 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 := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) - if value.Cmp(upperLimit) >= 0 { - return Value{}, fmt.Errorf("passed value larger than uint size %d", size) - } - return Value{ - ABIType: typeUint, - value: value, - }, nil -} - -// MakeUfixed takes a big rational number representation, a type size, 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) >= (1 << 16) { - return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") - } - 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) >= (1 << 16) { - return Value{}, fmt.Errorf("static array make error: pass in argument number larger than 2^16") - } else if len(values) == 0 { - return Value{}, fmt.Errorf("static array make error: 0 argument 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) >= (1 << 16) { - return Value{}, fmt.Errorf("tuple make error: pass in argument number larger than 2^16") - } - 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, - } -} - -// GetUint8 tries to retreve an uint8 from an ABI Value. -func (v Value) GetUint8() (uint8, error) { - if v.ABIType.enumIndex != Uint || v.ABIType.size > 8 { - return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 16 { - return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 32 { - return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 64 { - return 0, fmt.Errorf("value type unmatch or size 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.enumIndex != Uint { - return nil, fmt.Errorf("value type unmatch") - } - bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) - if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than uint size") - } - return bigIntForm, nil -} - -// GetUfixed tries to retrieve an big rational number from an ABI Value. -func (v Value) GetUfixed() (*big.Int, error) { - if v.ABIType.enumIndex != Ufixed { - return nil, fmt.Errorf("value type unmatch, should be ufixed") - } - bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) - if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than ufixed size") - } - return bigIntForm, nil -} - -// GetString tries to retrieve a string from ABI Value. -func (v Value) GetString() (string, error) { - if v.ABIType.enumIndex != String { - return "", fmt.Errorf("value type unmatch, 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.enumIndex != Byte { - return byte(0), fmt.Errorf("value type unmatch, 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.enumIndex != Address { - return [32]byte{}, fmt.Errorf("value type unmatch, 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.enumIndex { - 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.enumIndex != Bool { - return false, fmt.Errorf("value type unmatch, should be bool") - } - boolForm := v.value.(bool) - return boolForm, nil -} diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index edf88a4cc6..df4fdfdf7b 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -380,6 +380,28 @@ func (t Type) IsDynamic() bool { } } +// 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].enumIndex == Bool { + if curr != len(typeList)-1 && delta > 0 { + until++ + } else if curr > 0 && delta < 0 { + until++ + } else { + break + } + } else { + until-- + break + } + } + return until +} + // ByteLen method calculates the byte length of a static ABI type. func (t Type) ByteLen() (int, error) { switch t.enumIndex { diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go new file mode 100644 index 0000000000..c8e3155c0d --- /dev/null +++ b/data/abi/abi_value.go @@ -0,0 +1,304 @@ +// 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/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, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 8) +} + +// MakeUint16 takes a go `uint16` and gives an ABI Value of ABI type `uint16`. +func MakeUint16(value uint16) (Value, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 16) +} + +// MakeUint32 takes a go `uint32` and gives an ABI Value of ABI type `uint32`. +func MakeUint32(value uint32) (Value, error) { + bigInt := big.NewInt(int64(value)) + return MakeUint(bigInt, 32) +} + +// MakeUint64 takes a go `uint64` and gives an ABI Value of ABI type `uint64`. +func MakeUint64(value uint64) (Value, error) { + bigInt := big.NewInt(int64(0)).SetUint64(value) + return MakeUint(bigInt, 64) +} + +// MakeUint takes a big integer representation and a type size, +// 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 := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + if value.Cmp(upperLimit) >= 0 { + return Value{}, fmt.Errorf("passed value larger than uint size %d", size) + } + return Value{ + ABIType: typeUint, + value: value, + }, nil +} + +// MakeUfixed takes a big rational number representation, a type size, 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) >= (1 << 16) { + return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") + } + 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) >= (1 << 16) { + return Value{}, fmt.Errorf("static array make error: pass in argument number larger than 2^16") + } else if len(values) == 0 { + return Value{}, fmt.Errorf("static array make error: 0 argument 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) >= (1 << 16) { + return Value{}, fmt.Errorf("tuple make error: pass in argument number larger than 2^16") + } + 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, + } +} + +// GetUint8 tries to retreve an uint8 from an ABI Value. +func (v Value) GetUint8() (uint8, error) { + if v.ABIType.enumIndex != Uint || v.ABIType.size > 8 { + return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 16 { + return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 32 { + return 0, fmt.Errorf("value type unmatch or size 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 v.ABIType.enumIndex != Uint || v.ABIType.size > 64 { + return 0, fmt.Errorf("value type unmatch or size 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.enumIndex != Uint { + return nil, fmt.Errorf("value type unmatch") + } + bigIntForm := v.value.(*big.Int) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) + if sizeThreshold.Cmp(bigIntForm) <= 0 { + return nil, fmt.Errorf("value is larger than uint size") + } + return bigIntForm, nil +} + +// GetUfixed tries to retrieve an big rational number from an ABI Value. +func (v Value) GetUfixed() (*big.Int, error) { + if v.ABIType.enumIndex != Ufixed { + return nil, fmt.Errorf("value type unmatch, should be ufixed") + } + bigIntForm := v.value.(*big.Int) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) + if sizeThreshold.Cmp(bigIntForm) <= 0 { + return nil, fmt.Errorf("value is larger than ufixed size") + } + return bigIntForm, nil +} + +// GetString tries to retrieve a string from ABI Value. +func (v Value) GetString() (string, error) { + if v.ABIType.enumIndex != String { + return "", fmt.Errorf("value type unmatch, 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.enumIndex != Byte { + return byte(0), fmt.Errorf("value type unmatch, 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.enumIndex != Address { + return [32]byte{}, fmt.Errorf("value type unmatch, 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.enumIndex { + 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.enumIndex != Bool { + return false, fmt.Errorf("value type unmatch, should be bool") + } + boolForm := v.value.(bool) + return boolForm, nil +} From 93abb82c5e1644cde50e5319800bcb5827f18149 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 10 Sep 2021 18:26:22 -0400 Subject: [PATCH 17/29] resolve reviews, work on random gen tuple value encode/decode --- data/abi/abi_encode.go | 94 +++++++++++------- data/abi/abi_encode_test.go | 105 ++++++++++++++------ data/abi/abi_type.go | 188 ++++++++++++++++++++++-------------- data/abi/abi_type_test.go | 88 ++++++++--------- data/abi/abi_value.go | 48 ++++----- 5 files changed, 315 insertions(+), 208 deletions(-) diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index e18280bcda..90d13c2070 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -28,7 +28,7 @@ func (v Value) arrayToTuple() (Value, error) { var childT []Type var valueArr []Value - switch v.ABIType.enumIndex { + switch v.ABIType.abiTypeID { case String: strValue, err := v.GetString() if err != nil { @@ -49,10 +49,10 @@ func (v Value) arrayToTuple() (Value, error) { return Value{}, err } - childT = make([]Type, 32) - valueArr = make([]Value, 32) + childT = make([]Type, addressByteSize) + valueArr = make([]Value, addressByteSize) - for i := 0; i < 32; i++ { + for i := 0; i < addressByteSize; i++ { childT[i] = MakeByteType() valueArr[i] = MakeByte(addr[i]) } @@ -86,15 +86,15 @@ func (v Value) arrayToTuple() (Value, error) { // Encode method serialize the ABI value into a byte string of ABI encoding rule. func (v Value) Encode() ([]byte, error) { - switch v.ABIType.enumIndex { + 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 + // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use `fillbytes` bigIntBytes := bigIntValue.Bytes() - buffer := make([]byte, v.ABIType.size/8-uint16(len(bigIntBytes))) + buffer := make([]byte, v.ABIType.bitSize/8-uint16(len(bigIntBytes))) buffer = append(buffer, bigIntBytes...) return buffer, nil case Ufixed: @@ -102,9 +102,9 @@ func (v Value) Encode() ([]byte, error) { if err != nil { return []byte{}, err } - // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use fillbytes + // NOTE: ugly work-round for golang 1.14. if upgraded to 1.15, should use `fillbytes` encodeBuffer := ufixedValue.Bytes() - buffer := make([]byte, v.ABIType.size/8-uint16(len(encodeBuffer))) + buffer := make([]byte, v.ABIType.bitSize/8-uint16(len(encodeBuffer))) buffer = append(buffer, encodeBuffer...) return buffer, nil case Bool: @@ -134,7 +134,7 @@ func (v Value) Encode() ([]byte, error) { return []byte{}, err } length := len(convertedTuple.ABIType.childTypes) - lengthEncode := make([]byte, 2) + lengthEncode := make([]byte, lengthEncodeByteSize) binary.BigEndian.PutUint16(lengthEncode, uint16(length)) encoded, err := tupleEncoding(convertedTuple) @@ -153,10 +153,10 @@ func (v Value) Encode() ([]byte, error) { func compressMultipleBool(valueList []Value) (uint8, error) { var res uint8 = 0 if len(valueList) > 8 { - return 0, fmt.Errorf("value list passed in should be less than length 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.enumIndex != Bool { + if valueList[i].ABIType.abiTypeID != Bool { return 0, fmt.Errorf("bool type not matching in compressMultipleBool") } boolVal, err := valueList[i].GetBool() @@ -172,7 +172,7 @@ func compressMultipleBool(valueList []Value) (uint8, error) { // tupleEncoding encodes an ABI value of tuple type into an ABI encoded byte string. func tupleEncoding(v Value) ([]byte, error) { - if v.ABIType.enumIndex != Tuple { + if v.ABIType.abiTypeID != Tuple { return []byte{}, fmt.Errorf("type not supported in tupleEncoding") } if len(v.ABIType.childTypes) >= (1 << 16) { @@ -183,14 +183,19 @@ func tupleEncoding(v Value) ([]byte, error) { 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 { @@ -198,7 +203,7 @@ func tupleEncoding(v Value) ([]byte, error) { } tails[i] = tailEncoding } else { - if tupleElems[i].ABIType.enumIndex == Bool { + if tupleElems[i].ABIType.abiTypeID == Bool { // search previous bool before := findBoolLR(v.ABIType.childTypes, i, -1) // search after bool @@ -228,23 +233,31 @@ func tupleEncoding(v Value) ([]byte, error) { } // 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 head, tail := make([]byte, 0), make([]byte, 0) for i := 0; i < len(v.ABIType.childTypes); i++ { head = append(head, heads[i]...) @@ -256,28 +269,35 @@ func tupleEncoding(v Value) ([]byte, error) { // 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.enumIndex { + switch valueType.abiTypeID { case Uint: - if len(valueByte) != int(valueType.size)/8 { + if len(valueByte) != int(valueType.bitSize)/8 { return Value{}, fmt.Errorf("uint%d decode: expected byte length %d, but got byte length %d", - valueType.size, valueType.size/8, len(valueByte)) + valueType.bitSize, valueType.bitSize/8, len(valueByte)) } uintValue := big.NewInt(0).SetBytes(valueByte) - return MakeUint(uintValue, valueType.size) + return MakeUint(uintValue, valueType.bitSize) case Ufixed: - if len(valueByte) != int(valueType.size)/8 { + if len(valueByte) != int(valueType.bitSize)/8 { return Value{}, fmt.Errorf("ufixed%dx%d decode: expected length %d, got byte length %d", - valueType.size, valueType.precision, valueType.size/8, len(valueByte)) + valueType.bitSize, valueType.precision, valueType.bitSize/8, len(valueByte)) } ufixedNumerator := big.NewInt(0).SetBytes(valueByte) - return MakeUfixed(ufixedNumerator, valueType.size, valueType.precision) + return MakeUfixed(ufixedNumerator, valueType.bitSize, valueType.precision) case Bool: if len(valueByte) != 1 { return Value{}, fmt.Errorf("boolean byte should be length 1 byte") } - boolValue := valueByte[0] > 0 + 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 { @@ -300,17 +320,17 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { tupleDecoded.ABIType = valueType return tupleDecoded, nil case Address: - if len(valueByte) != 32 { + if len(valueByte) != addressByteSize { return Value{}, fmt.Errorf("address should be length 32") } - var byteAssign [32]byte + var byteAssign [addressByteSize]byte copy(byteAssign[:], valueByte) return MakeAddress(byteAssign), nil case ArrayDynamic: - if len(valueByte) < 2 { + if len(valueByte) < lengthEncodeByteSize { return Value{}, fmt.Errorf("dynamic array format corrupted") } - dynamicLen := binary.BigEndian.Uint16(valueByte[:2]) + dynamicLen := binary.BigEndian.Uint16(valueByte[:lengthEncodeByteSize]) childT := make([]Type, dynamicLen) for i := 0; i < int(dynamicLen); i++ { childT[i] = valueType.childTypes[0] @@ -319,22 +339,22 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { if err != nil { return Value{}, err } - tupleDecoded, err := tupleDecoding(valueByte[2:], converted) + tupleDecoded, err := tupleDecoding(valueByte[lengthEncodeByteSize:], converted) if err != nil { return Value{}, err } tupleDecoded.ABIType = valueType return tupleDecoded, nil case String: - if len(valueByte) < 2 { + if len(valueByte) < lengthEncodeByteSize { return Value{}, fmt.Errorf("string format corrupted") } - stringLenBytes := valueByte[:2] + stringLenBytes := valueByte[:lengthEncodeByteSize] byteLen := binary.BigEndian.Uint16(stringLenBytes) - if len(valueByte[2:]) != int(byteLen) { + if len(valueByte[lengthEncodeByteSize:]) != int(byteLen) { return Value{}, fmt.Errorf("string representation in byte: length not matching") } - return MakeString(string(valueByte[2:])), nil + return MakeString(string(valueByte[lengthEncodeByteSize:])), nil case Tuple: return tupleDecoding(valueByte, valueType) default: @@ -351,22 +371,24 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { for i := 0; i < len(valueType.childTypes); i++ { if valueType.childTypes[i].IsDynamic() { - if len(valueBytes[iterIndex:]) < 2 { + if len(valueBytes[iterIndex:]) < lengthEncodeByteSize { return Value{}, fmt.Errorf("ill formed tuple dynamic typed value encoding") } - dynamicIndex := binary.BigEndian.Uint16(valueBytes[iterIndex : iterIndex+2]) + 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 += 2 + iterIndex += lengthEncodeByteSize } else { // if bool ... - if valueType.childTypes[i].enumIndex == Bool { + if valueType.childTypes[i].abiTypeID == Bool { // search previous bool before := findBoolLR(valueType.childTypes, i, -1) // search after bool @@ -412,6 +434,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } // 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") @@ -429,6 +452,7 @@ func tupleDecoding(valueBytes []byte, valueType Type) (Value, error) { } } + // 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]) diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index f39944f93a..73e205b2dc 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -49,8 +49,8 @@ func TestEncodeValid(t *testing.T) { require.NoError(t, err, "uint encode fail") require.Equal(t, expected, uintBytesActual, "encode uint not match with expected") } - // 2^[size] - 1 test - // check if uint can contain max uint value (2^size - 1) + // 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)), @@ -62,9 +62,9 @@ func TestEncodeValid(t *testing.T) { require.Equal(t, largest.Bytes(), encoded, "encode uint largest do not match with expected") } - // encoding test for ufixed, iterating through all the valid ufixed size and precision + // 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^size - 1) under specific byte size + // 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( @@ -87,7 +87,7 @@ func TestEncodeValid(t *testing.T) { buffer = append(buffer, randomBytes...) require.Equal(t, buffer, encodedUfixed, "encode ufixed not match with expected") } - // (2^[size] - 1) / (10^[precision]) test + // (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() @@ -351,8 +351,8 @@ func TestEncodeValid(t *testing.T) { func TestDecodeValid(t *testing.T) { partitiontest.PartitionTest(t) - // decoding test for uint, iterating through all valid uint size - // randomly take 1000 tests on each valid size + // 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)) @@ -372,7 +372,7 @@ func TestDecodeValid(t *testing.T) { } } - // decoding test for ufixed, iterating through all valid ufixed size and precision + // 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 { @@ -587,7 +587,7 @@ func TestDecodeValid(t *testing.T) { actual, err := Decode( inputEncode, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), @@ -623,16 +623,16 @@ func TestDecodeValid(t *testing.T) { 0b11000000, } decoded, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, @@ -670,16 +670,16 @@ func TestDecodeValid(t *testing.T) { 0x00, 0x02, 0b11000000, } decoded, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, }, @@ -708,15 +708,15 @@ func TestDecodeValid(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } decoded, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, }, @@ -733,7 +733,7 @@ func TestDecodeValid(t *testing.T) { require.NoError(t, err, "make empty tuple should not return error") encodedInput := make([]byte, 0) decoded, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 0, childTypes: []Type{}, }) @@ -873,7 +873,7 @@ func TestDecodeInvalid(t *testing.T) { _, err := Decode( inputEncode, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeStringType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), MakeBoolType(), @@ -899,16 +899,16 @@ func TestDecodeInvalid(t *testing.T) { */ t.Run("corrupted static bool array tuple decoding", func(t *testing.T) { expectedType := Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, @@ -947,16 +947,16 @@ func TestDecodeInvalid(t *testing.T) { 0x00, 0x02, 0b11000000, } _, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, staticLength: 2, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, }, @@ -984,15 +984,15 @@ func TestDecodeInvalid(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } _, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 2, childTypes: []Type{ { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, { - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{MakeBoolType()}, }, }, @@ -1007,10 +1007,55 @@ func TestDecodeInvalid(t *testing.T) { t.Run("corrupted empty tuple decoding", func(t *testing.T) { encodedInput := []byte{0xFF} _, err := Decode(encodedInput, Type{ - enumIndex: Tuple, + abiTypeID: Tuple, staticLength: 0, childTypes: []Type{}, }) require.Error(t, err, "decode corrupted empty tuple should return error") }) } + +func TestEncodeDecodeRandomTuple(t *testing.T) { + partitiontest.PartitionTest(t) + var testValuePool [][]Value = make([][]Value, 8) + 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) + } + } +} diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index df4fdfdf7b..b13f9403e0 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -61,11 +61,11 @@ const ( // Type is the struct that stores information about an ABI value's type. type Type struct { - enumIndex BaseType + abiTypeID BaseType childTypes []Type - // only can be applied to `uint` size or `ufixed` size - size uint16 + // only can be applied to `uint` bitSize or `ufixed` bitSize + bitSize uint16 // only can be applied to `ufixed` precision precision uint16 @@ -81,13 +81,13 @@ type Type struct { // String serialize an ABI Type to a string in ABI encoding. func (t Type) String() string { - switch t.enumIndex { + switch t.abiTypeID { case Uint: - return "uint" + strconv.Itoa(int(t.size)) + return "uint" + strconv.Itoa(int(t.bitSize)) case Byte: return "byte" case Ufixed: - return "ufixed" + strconv.Itoa(int(t.size)) + "x" + strconv.Itoa(int(t.precision)) + return "ufixed" + strconv.Itoa(int(t.bitSize)) + "x" + strconv.Itoa(int(t.precision)) case Bool: return "bool" case ArrayStatic: @@ -109,23 +109,55 @@ func (t Type) String() string { } } +var staticArrayRegexp *regexp.Regexp = nil +var ufixedRegexp *regexp.Regexp = nil +var regexpInitialized = false + +func initRegexps() (err error) { + if regexpInitialized { + return nil + } + // Note that we allow only decimal static array length + staticArrayRegexp, err = regexp.Compile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`) + if err != nil { + return + } + ufixedRegexp, err = regexp.Compile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`) + if err != nil { + return + } + regexpInitialized = true + return +} + // TypeFromString de-serialize ABI type from a string following ABI encoding. func TypeFromString(str string) (Type, error) { + // if the regexps for type str matching are not initialized + // attempt to initialize with `initRegexps` + // if error return err + if !regexpInitialized { + err := initRegexps() + if err != nil { + return Type{}, err + } + } + switch { case strings.HasSuffix(str, "[]"): arrayArgType, err := TypeFromString(str[:len(str)-2]) if err != nil { - return arrayArgType, err + return Type{}, err } return MakeDynamicArrayType(arrayArgType), nil case strings.HasSuffix(str, "]"): - stringMatches := regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`).FindStringSubmatch(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 @@ -145,8 +177,8 @@ func TypeFromString(str string) (Type, error) { case str == "byte": return MakeByteType(), nil case strings.HasPrefix(str, "ufixed"): - stringMatches := regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`).FindStringSubmatch(str) - // match string itself, then type-size, and type-precision + 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) } @@ -190,14 +222,33 @@ 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) { - // argument str is the content between parentheses of tuple, i.e. - // (...... str ......) - // ^ ^ - parenSegmentRecord := make([]segment, 0) - stack := make([]int, 0) + // 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: cannot replace tuple segment back: " + + "number of empty placeholders do not match with number of sub-tuples") + } + + // 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) @@ -219,11 +270,6 @@ func parseTupleContent(str string) ([]string, error) { return []string{}, fmt.Errorf("unpaired parentheses: %s", str) } - // kudos to Jason Paulos - if strings.Contains(str, ",,") { - return []string{}, fmt.Errorf("no consecutive commas") - } - // take out tuple-formed type str in tuple argument strCopied := str for i := len(parenSegmentRecord) - 1; i >= 0; i-- { @@ -231,64 +277,55 @@ func parseTupleContent(str string) ([]string, error) { strCopied = strCopied[:parenSeg.left] + strCopied[parenSeg.right+1:] } - // maintain list of empty strings as placeholders for tuple-formed type str + // split the string without parenthesis segments tupleStrSegs := strings.Split(strCopied, ",") - emptyStrIndex := make([]int, 0) + + // 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 == "" { - emptyStrIndex = append(emptyStrIndex, index) + parenSeg := parenSegmentRecord[parenSegCount] + tupleStrSegs[index] = str[parenSeg.left : parenSeg.right+1] + parenSegCount++ } } - // check if the number of empty block placeholder is equal to number of sub-tuples - // if number do not match, this might be incurred by head/tail commas - // e.g. (,uint64,(bool,bool)) => ["", uint64, ""], with sub-tuple ["(bool,bool)"] - if len(emptyStrIndex) != len(parenSegmentRecord) { - return []string{}, - fmt.Errorf("parsing error: cannot replace tuple segment back: " + - "number of empty placeholders do not match with number of sub-tuples") - } - - // replace back the tuple-formed type str - for index, replaceIndex := range emptyStrIndex { - tupleStrSegs[replaceIndex] = str[parenSegmentRecord[index].left : parenSegmentRecord[index].right+1] - } - return tupleStrSegs, nil } -// MakeUintType makes `Uint` ABI type by taking a type size argument. -// The range of type size is [8, 512] and type size % 8 == 0. +// 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 size: %d", typeSize) + return Type{}, fmt.Errorf("unsupported uint type bitSize: %d", typeSize) } return Type{ - enumIndex: Uint, - size: typeSize, + abiTypeID: Uint, + bitSize: typeSize, }, nil } // MakeByteType makes `Byte` ABI type. func MakeByteType() Type { return Type{ - enumIndex: Byte, + abiTypeID: Byte, } } -// MakeUfixedType makes `UFixed` ABI type by taking type size and type precision as arguments. -// The range of type size is [8, 512] and type size % 8 == 0. +// 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 size: %d", typeSize) + 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{ - enumIndex: Ufixed, - size: typeSize, + abiTypeID: Ufixed, + bitSize: typeSize, precision: typePrecision, }, nil } @@ -296,7 +333,7 @@ func MakeUfixedType(typeSize uint16, typePrecision uint16) (Type, error) { // MakeBoolType makes `Bool` ABI type. func MakeBoolType() Type { return Type{ - enumIndex: Bool, + abiTypeID: Bool, } } @@ -304,7 +341,7 @@ func MakeBoolType() Type { // array element type and array length as arguments. func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { return Type{ - enumIndex: ArrayStatic, + abiTypeID: ArrayStatic, childTypes: []Type{argumentType}, staticLength: arrayLength, } @@ -313,14 +350,14 @@ func MakeStaticArrayType(argumentType Type, arrayLength uint16) Type { // MakeAddressType makes `Address` ABI type. func MakeAddressType() Type { return Type{ - enumIndex: Address, + abiTypeID: Address, } } // MakeDynamicArrayType makes dynamic length array by taking array element type as argument. func MakeDynamicArrayType(argumentType Type) Type { return Type{ - enumIndex: ArrayDynamic, + abiTypeID: ArrayDynamic, childTypes: []Type{argumentType}, } } @@ -328,7 +365,7 @@ func MakeDynamicArrayType(argumentType Type) Type { // MakeStringType makes `String` ABI type. func MakeStringType() Type { return Type{ - enumIndex: String, + abiTypeID: String, } } @@ -338,7 +375,7 @@ func MakeTupleType(argumentTypes []Type) (Type, error) { return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") } return Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: argumentTypes, staticLength: uint16(len(argumentTypes)), }, nil @@ -346,9 +383,9 @@ func MakeTupleType(argumentTypes []Type) (Type, error) { // Equal method decides the equality of two types: t == t0. func (t Type) Equal(t0 Type) bool { - if t.enumIndex != t0.enumIndex { + if t.abiTypeID != t0.abiTypeID { return false - } else if t.precision != t0.precision || t.size != t0.size { + } else if t.precision != t0.precision || t.bitSize != t0.bitSize { return false } else if t.staticLength != t0.staticLength { return false @@ -367,7 +404,7 @@ func (t Type) Equal(t0 Type) bool { // IsDynamic method decides if an ABI type is dynamic or static. func (t Type) IsDynamic() bool { - switch t.enumIndex { + switch t.abiTypeID { case ArrayDynamic, String: return true default: @@ -386,7 +423,7 @@ func findBoolLR(typeList []Type, index int, delta int) int { until := 0 for { curr := index + delta*until - if typeList[curr].enumIndex == Bool { + if typeList[curr].abiTypeID == Bool { if curr != len(typeList)-1 && delta > 0 { until++ } else if curr > 0 && delta < 0 { @@ -402,19 +439,26 @@ func findBoolLR(typeList []Type, index int, delta int) int { 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.enumIndex { + switch t.abiTypeID { case Address: - return 32, nil + return addressByteSize, nil case Byte: - return 1, nil + return singleByteSize, nil case Uint, Ufixed: - return int(t.size / 8), nil + return int(t.bitSize / 8), nil case Bool: - return 1, nil + return singleBoolSize, nil case ArrayStatic: - if t.childTypes[0].enumIndex == Bool { + if t.childTypes[0].abiTypeID == Bool { byteLen := int(t.staticLength) / 8 if t.staticLength%8 != 0 { byteLen++ @@ -429,20 +473,14 @@ func (t Type) ByteLen() (int, error) { case Tuple: size := 0 for i := 0; i < len(t.childTypes); i++ { - if t.childTypes[i].enumIndex == Bool { - // search previous bool - before := findBoolLR(t.childTypes, i, -1) + if t.childTypes[i].abiTypeID == Bool { // search after bool after := findBoolLR(t.childTypes, i, 1) - // append to heads and tails - if before%8 != 0 { - return -1, fmt.Errorf("expected before has number of bool mod 8 = 0") - } - if after > 7 { - after = 7 - } i += after - size++ + size = after / 8 + if after%8 != 0 { + size++ + } } else { childByteSize, err := t.childTypes[i].ByteLen() if err != nil { diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 7e8db52d52..752ff5b8c8 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -61,8 +61,8 @@ func TestMakeTypeValid(t *testing.T) { { input: MakeDynamicArrayType( Type{ - enumIndex: Uint, - size: uint16(32), + abiTypeID: Uint, + bitSize: uint16(32), }, ), testType: "dynamic array", @@ -80,8 +80,8 @@ func TestMakeTypeValid(t *testing.T) { { input: MakeStaticArrayType( Type{ - enumIndex: Ufixed, - size: uint16(128), + abiTypeID: Ufixed, + bitSize: uint16(128), precision: uint16(10), }, uint16(100), @@ -103,22 +103,22 @@ func TestMakeTypeValid(t *testing.T) { // tuple type { input: Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ { - enumIndex: Uint, - size: uint16(32), + abiTypeID: Uint, + bitSize: uint16(32), }, { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - enumIndex: Ufixed, - size: uint16(256), + abiTypeID: Ufixed, + bitSize: uint16(256), precision: uint16(10), }, ), @@ -152,7 +152,7 @@ func TestMakeTypeInvalid(t *testing.T) { } // 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 size input %d", randInput) + require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randInput) } // ufixed for i := 0; i <= 10000; i++ { @@ -165,7 +165,7 @@ func TestMakeTypeInvalid(t *testing.T) { randPrecision = rand.Uint32() } _, err := MakeUfixedType(uint16(randSize), uint16(randPrecision)) - require.Error(t, err, "MakeUintType: should throw error on size input %d", randSize) + require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize) } } @@ -201,15 +201,15 @@ func TestTypeFromStringValid(t *testing.T) { { input: "uint256[]", testType: "dynamic array", - expected: MakeDynamicArrayType(Type{enumIndex: Uint, size: 256}), + expected: MakeDynamicArrayType(Type{abiTypeID: Uint, bitSize: 256}), }, { input: "ufixed256x64[]", testType: "dynamic array", expected: MakeDynamicArrayType( Type{ - enumIndex: Ufixed, - size: 256, + abiTypeID: Ufixed, + bitSize: 256, precision: 64, }, ), @@ -241,7 +241,7 @@ func TestTypeFromStringValid(t *testing.T) { testType: "static array", expected: MakeStaticArrayType( MakeDynamicArrayType( - Type{enumIndex: Uint, size: uint16(64)}, + Type{abiTypeID: Uint, bitSize: uint16(64)}, ), uint16(200), ), @@ -251,22 +251,22 @@ func TestTypeFromStringValid(t *testing.T) { input: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", testType: "tuple type", expected: Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ { - enumIndex: Uint, - size: uint16(32), + abiTypeID: Uint, + bitSize: uint16(32), }, { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - enumIndex: Ufixed, - size: uint16(256), + abiTypeID: Ufixed, + bitSize: uint16(256), precision: uint16(10), }, ), @@ -282,25 +282,25 @@ func TestTypeFromStringValid(t *testing.T) { input: "(uint32,(address,byte,bool[10],(ufixed256x10[])))", testType: "tuple type", expected: Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ { - enumIndex: Uint, - size: uint16(32), + abiTypeID: Uint, + bitSize: uint16(32), }, { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeAddressType(), MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeDynamicArrayType( Type{ - enumIndex: Ufixed, - size: uint16(256), + abiTypeID: Ufixed, + bitSize: uint16(256), precision: uint16(10), }, ), @@ -318,31 +318,31 @@ func TestTypeFromStringValid(t *testing.T) { input: "((uint32),(address,(byte,bool[10],ufixed256x10[])))", testType: "tuple type", expected: Type{ - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ { - enumIndex: Uint, - size: uint16(32), + abiTypeID: Uint, + bitSize: uint16(32), }, }, staticLength: 1, }, { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeAddressType(), { - enumIndex: Tuple, + abiTypeID: Tuple, childTypes: []Type{ MakeByteType(), MakeStaticArrayType(MakeBoolType(), uint16(10)), MakeDynamicArrayType( Type{ - enumIndex: Ufixed, - size: uint16(256), + abiTypeID: Ufixed, + bitSize: uint16(256), precision: uint16(10), }, ), @@ -376,7 +376,7 @@ func TestTypeFromStringInvalid(t *testing.T) { } errorInput := "uint" + strconv.FormatUint(randSize, 10) _, err := TypeFromString(errorInput) - require.Error(t, err, "MakeUintType: should throw error on size input %d", randSize) + require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize) } for i := 0; i <= 10000; i++ { randSize := rand.Uint64() @@ -389,7 +389,7 @@ func TestTypeFromStringInvalid(t *testing.T) { } errorInput := "ufixed" + strconv.FormatUint(randSize, 10) + "x" + strconv.FormatUint(randPrecision, 10) _, err := TypeFromString(errorInput) - require.Error(t, err, "MakeUintType: should throw error on size input %d", randSize) + require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize) } var testcases = []string{ // uint @@ -453,7 +453,7 @@ func generateTupleType(baseTypes []Type, tupleTypes []Type) Type { resultTypes[i] = baseTypes[rand.Intn(len(baseTypes))] } } - return Type{enumIndex: Tuple, childTypes: resultTypes, staticLength: uint16(tupleLen)} + return Type{abiTypeID: Tuple, childTypes: resultTypes, staticLength: uint16(tupleLen)} } func TestTypeMISC(t *testing.T) { @@ -551,10 +551,10 @@ func TestTypeMISC(t *testing.T) { require.Error(t, err, "byteLen test error on %s dynamic type, should have error", testType.String()) } else { - if testType.enumIndex == Tuple { + if testType.abiTypeID == Tuple { sizeSum := 0 for i := 0; i < len(testType.childTypes); i++ { - if testType.childTypes[i].enumIndex == Bool { + if testType.childTypes[i].abiTypeID == Bool { // search previous bool before := findBoolLR(testType.childTypes, i, -1) // search after bool @@ -575,8 +575,8 @@ func TestTypeMISC(t *testing.T) { require.Equal(t, sizeSum, byteLen, "%s do not match calculated byte length %d", testType.String(), sizeSum) - } else if testType.enumIndex == ArrayStatic { - if testType.childTypes[0].enumIndex == Bool { + } else if testType.abiTypeID == ArrayStatic { + if testType.childTypes[0].abiTypeID == Bool { expected := testType.staticLength / 8 if testType.staticLength%8 != 0 { expected++ diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go index c8e3155c0d..7091c50813 100644 --- a/data/abi/abi_value.go +++ b/data/abi/abi_value.go @@ -51,8 +51,8 @@ func MakeUint64(value uint64) (Value, error) { return MakeUint(bigInt, 64) } -// MakeUint takes a big integer representation and a type size, -// and returns an ABI Value of ABI Uint type. +// 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 { @@ -60,7 +60,7 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { } upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) if value.Cmp(upperLimit) >= 0 { - return Value{}, fmt.Errorf("passed value larger than uint size %d", size) + return Value{}, fmt.Errorf("passed value larger than uint bitSize %d", size) } return Value{ ABIType: typeUint, @@ -68,8 +68,8 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { }, nil } -// MakeUfixed takes a big rational number representation, a type size, and a type precision, -// and returns an ABI Value of ABI UFixedx +// MakeUfixed takes a big rational number 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 { @@ -175,8 +175,8 @@ func MakeBool(value bool) Value { // GetUint8 tries to retreve an uint8 from an ABI Value. func (v Value) GetUint8() (uint8, error) { - if v.ABIType.enumIndex != Uint || v.ABIType.size > 8 { - return 0, fmt.Errorf("value type unmatch or size too large") + if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 8 { + return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -187,8 +187,8 @@ func (v Value) GetUint8() (uint8, error) { // GetUint16 tries to retrieve an uint16 from an ABI Value. func (v Value) GetUint16() (uint16, error) { - if v.ABIType.enumIndex != Uint || v.ABIType.size > 16 { - return 0, fmt.Errorf("value type unmatch or size too large") + if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 16 { + return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -199,8 +199,8 @@ func (v Value) GetUint16() (uint16, error) { // GetUint32 tries to retrieve an uint32 from an ABI Value. func (v Value) GetUint32() (uint32, error) { - if v.ABIType.enumIndex != Uint || v.ABIType.size > 32 { - return 0, fmt.Errorf("value type unmatch or size too large") + if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 32 { + return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -211,8 +211,8 @@ func (v Value) GetUint32() (uint32, error) { // GetUint64 tries to retrieve an uint64 from an ABI Value. func (v Value) GetUint64() (uint64, error) { - if v.ABIType.enumIndex != Uint || v.ABIType.size > 64 { - return 0, fmt.Errorf("value type unmatch or size too large") + if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 64 { + return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -223,33 +223,33 @@ func (v Value) GetUint64() (uint64, error) { // GetUint tries to retrieve an big uint from an ABI Value. func (v Value) GetUint() (*big.Int, error) { - if v.ABIType.enumIndex != Uint { + if v.ABIType.abiTypeID != Uint { return nil, fmt.Errorf("value type unmatch") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than uint size") + return nil, fmt.Errorf("value is larger than uint bitSize") } return bigIntForm, nil } // GetUfixed tries to retrieve an big rational number from an ABI Value. func (v Value) GetUfixed() (*big.Int, error) { - if v.ABIType.enumIndex != Ufixed { + if v.ABIType.abiTypeID != Ufixed { return nil, fmt.Errorf("value type unmatch, should be ufixed") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.size)) + sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than ufixed size") + return nil, fmt.Errorf("value is larger than ufixed bitSize") } return bigIntForm, nil } // GetString tries to retrieve a string from ABI Value. func (v Value) GetString() (string, error) { - if v.ABIType.enumIndex != String { + if v.ABIType.abiTypeID != String { return "", fmt.Errorf("value type unmatch, should be ufixed") } stringForm := v.value.(string) @@ -258,7 +258,7 @@ func (v Value) GetString() (string, error) { // GetByte tries to retrieve a byte from ABI Value. func (v Value) GetByte() (byte, error) { - if v.ABIType.enumIndex != Byte { + if v.ABIType.abiTypeID != Byte { return byte(0), fmt.Errorf("value type unmatch, should be bytes") } bytesForm := v.value.(byte) @@ -267,7 +267,7 @@ func (v Value) GetByte() (byte, error) { // GetAddress tries to retrieve a [32]byte array from ABI Value. func (v Value) GetAddress() ([32]byte, error) { - if v.ABIType.enumIndex != Address { + if v.ABIType.abiTypeID != Address { return [32]byte{}, fmt.Errorf("value type unmatch, should be address") } addressForm := v.value.([32]byte) @@ -276,7 +276,7 @@ func (v Value) GetAddress() ([32]byte, error) { // GetValueByIndex retrieve value element by the index passed in func (v Value) GetValueByIndex(index uint16) (Value, error) { - switch v.ABIType.enumIndex { + switch v.ABIType.abiTypeID { case ArrayDynamic: elements := v.value.([]Value) if len(elements) <= int(index) { @@ -296,7 +296,7 @@ func (v Value) GetValueByIndex(index uint16) (Value, error) { // GetBool tries to retrieve a boolean value from the ABI Value. func (v Value) GetBool() (bool, error) { - if v.ABIType.enumIndex != Bool { + if v.ABIType.abiTypeID != Bool { return false, fmt.Errorf("value type unmatch, should be bool") } boolForm := v.value.(bool) From c23cad37906f44b46cd7168d24ade822b5f8c962 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 13 Sep 2021 17:53:12 -0400 Subject: [PATCH 18/29] add random tuple test --- data/abi/abi_encode_test.go | 136 +++++++++++++++++++++++++++++++++++- data/abi/abi_type.go | 7 +- data/abi/abi_type_test.go | 10 +-- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index 73e205b2dc..31edc9ea11 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -20,6 +20,7 @@ import ( "crypto/rand" "encoding/binary" "math/big" + mrand "math/rand" "testing" "github.com/algorand/go-algorand/test/partitiontest" @@ -1015,9 +1016,124 @@ func TestDecodeInvalid(t *testing.T) { }) } +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++ { + valIndex := mrand.Int31n(2) + 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++ { + valIndex := mrand.Int31n(2) + 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++ { + tupleLen := 1 + mrand.Int31n(25) + tupleValList := make([]Value, tupleLen) + for tupleElemIndex := 0; tupleElemIndex < int(tupleLen); tupleElemIndex++ { + tupleTypeIndex := mrand.Int31n(int32(slotRange)) + tupleElemChoiceRange := len((*testValuePool)[tupleTypeIndex]) + tupleElemRangeIndex := mrand.Intn(tupleElemChoiceRange) + 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) - var testValuePool [][]Value = make([][]Value, 8) + // 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++ { @@ -1058,4 +1174,22 @@ func TestEncodeDecodeRandomTuple(t *testing.T) { 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 index b13f9403e0..74ca172155 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -476,9 +476,12 @@ func (t Type) ByteLen() (int, error) { if t.childTypes[i].abiTypeID == Bool { // search after bool after := findBoolLR(t.childTypes, i, 1) + // shift the index i += after - size = after / 8 - if after%8 != 0 { + // get number of bool + boolNum := after + 1 + size += boolNum / 8 + if boolNum%8 != 0 { size++ } } else { diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 752ff5b8c8..e18fd59348 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -146,19 +146,19 @@ func TestMakeTypeInvalid(t *testing.T) { partitiontest.PartitionTest(t) // uint for i := 0; i <= 1000; i++ { - randInput := rand.Uint32() + randInput := rand.Uint32() % (1 << 16) for randInput%8 == 0 && randInput <= 512 && randInput >= 8 { - randInput = rand.Uint32() + 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", 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() + randSize := rand.Uint64() % (1 << 16) for randSize%8 == 0 && randSize <= 512 && randSize >= 8 { - randSize = rand.Uint64() + randSize = rand.Uint64() % (1 << 16) } randPrecision := rand.Uint32() for randPrecision >= 1 && randPrecision <= 160 { From ba68ea9ebf7326534a2432ab7fd1cb309a0ccc66 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 13 Sep 2021 18:49:08 -0400 Subject: [PATCH 19/29] remove math-rand, use crypto-rand --- data/abi/abi_encode_test.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index 31edc9ea11..2fed2d7b85 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -20,7 +20,6 @@ import ( "crypto/rand" "encoding/binary" "math/big" - mrand "math/rand" "testing" "github.com/algorand/go-algorand/test/partitiontest" @@ -1054,7 +1053,9 @@ func generateStaticArray(t *testing.T, testValuePool *[][]Value) { // bool boolArrayList := make([]Value, 20) for boolIndex := 0; boolIndex < 20; boolIndex++ { - valIndex := mrand.Int31n(2) + 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) @@ -1100,7 +1101,9 @@ func generateDynamicArray(t *testing.T, testValuePool *[][]Value) { // bool boolArrayList := make([]Value, 20) for boolIndex := 0; boolIndex < 20; boolIndex++ { - valIndex := mrand.Int31n(2) + 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()) @@ -1110,12 +1113,19 @@ func generateDynamicArray(t *testing.T, testValuePool *[][]Value) { func generateTuples(t *testing.T, testValuePool *[][]Value, slotRange int) { for i := 0; i < 100; i++ { - tupleLen := 1 + mrand.Int31n(25) + 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++ { - tupleTypeIndex := mrand.Int31n(int32(slotRange)) + 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]) - tupleElemRangeIndex := mrand.Intn(tupleElemChoiceRange) + + 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 } From 438ccd25f9be545123b36851dae5dbd4b2f15068 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 21 Sep 2021 15:09:31 -0400 Subject: [PATCH 20/29] minor --- data/abi/abi_type.go | 2 +- data/abi/abi_type_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index 74ca172155..a04dedb9fb 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -105,7 +105,7 @@ func (t Type) String() string { } return "(" + strings.Join(typeStrings, ",") + ")" default: - panic("Bruh you should not be here") + panic("Type Serialization Error, fail to infer from abiTypeID") } } diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index e18fd59348..94386fc229 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -175,7 +175,7 @@ func TestTypeFromStringValid(t *testing.T) { for i := 8; i <= 512; i += 8 { expected, _ := MakeUintType(uint16(i)) actual, err := TypeFromString(expected.String()) - require.Equal(t, nil, err, "TypeFromString: uint parsing error: %s", 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()) } @@ -184,7 +184,7 @@ func TestTypeFromStringValid(t *testing.T) { for j := 1; j <= 160; j++ { expected, _ := MakeUfixedType(uint16(i), uint16(j)) actual, err := TypeFromString("ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j)) - require.Equal(t, nil, err, "TypeFromString ufixed parsing error: %s", expected.String()) + 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()) } @@ -360,7 +360,7 @@ func TestTypeFromStringValid(t *testing.T) { for _, testcase := range testcases { t.Run(fmt.Sprintf("TypeFromString test %s", testcase.testType), func(t *testing.T) { actual, err := TypeFromString(testcase.input) - require.Equal(t, nil, err, "TypeFromString %s parsing error", testcase.testType) + 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()) }) From 825e98d7148a74a78cfb948166b19658b7c157fa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 21 Sep 2021 17:28:18 -0400 Subject: [PATCH 21/29] minor --- data/abi/abi_encode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index 90d13c2070..ac79bfc1d7 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -145,7 +145,7 @@ func (v Value) Encode() ([]byte, error) { case Tuple: return tupleEncoding(v) default: - return []byte{}, fmt.Errorf("bruh you should not be here in encoding: unknown type error") + return []byte{}, fmt.Errorf("Encoding: unknown type error") } } From 43ff5148c045f931ca7a3b634f2ae6fa19394948 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 21 Sep 2021 20:21:47 -0400 Subject: [PATCH 22/29] some change requested from community --- data/abi/abi_encode.go | 2 +- data/abi/abi_type.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index ac79bfc1d7..a8db78a385 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -145,7 +145,7 @@ func (v Value) Encode() ([]byte, error) { case Tuple: return tupleEncoding(v) default: - return []byte{}, fmt.Errorf("Encoding: unknown type error") + return []byte{}, fmt.Errorf("Encoding: unknown type error (bruh why you are here)") } } diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index a04dedb9fb..a3896eaa1f 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -105,7 +105,7 @@ func (t Type) String() string { } return "(" + strings.Join(typeStrings, ",") + ")" default: - panic("Type Serialization Error, fail to infer from abiTypeID") + panic("Type Serialization Error, fail to infer from abiTypeID (bruh you shouldn't be here)") } } From c757de5e3888102f2909a2e6312960f4f45ed300 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 13:51:06 -0400 Subject: [PATCH 23/29] fix for 1 corner case --- data/abi/abi_type.go | 8 +++++++- data/abi/abi_type_test.go | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index a3896eaa1f..b98a917a82 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -198,7 +198,7 @@ func TypeFromString(str string) (Type, error) { return MakeAddressType(), nil case str == "string": return MakeStringType(), nil - case len(str) > 2 && str[0] == '(' && str[len(str)-1] == ')': + case len(str) >= 2 && str[0] == '(' && str[len(str)-1] == ')': tupleContent, err := parseTupleContent(str[1 : len(str)-1]) if err != nil { return Type{}, err @@ -226,6 +226,12 @@ type segment struct{ left, right int } // (...... 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" diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 94386fc229..471ff54f48 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -247,6 +247,15 @@ func TestTypeFromStringValid(t *testing.T) { ), }, // 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", From 515ef05ac023cb5f72c98692ac7d93bc0b8c71bc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 15:32:45 -0400 Subject: [PATCH 24/29] resolve review comments --- data/abi/abi_encode.go | 16 +++++---- data/abi/abi_encode_test.go | 4 +-- data/abi/abi_type.go | 43 ++++++++-------------- data/abi/abi_type_test.go | 19 ++++++---- data/abi/abi_value.go | 71 ++++++++++++++++++++++--------------- 5 files changed, 79 insertions(+), 74 deletions(-) diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index a8db78a385..0452bbd082 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -258,12 +258,14 @@ func tupleEncoding(v Value) ([]byte, error) { } // concat everything as the abi encoded bytes - head, tail := make([]byte, 0), make([]byte, 0) - for i := 0; i < len(v.ABIType.childTypes); i++ { - head = append(head, heads[i]...) - tail = append(tail, tails[i]...) + encoded := make([]byte, 0) + for _, head := range heads { + encoded = append(encoded, head...) + } + for _, tail := range tails { + encoded = append(encoded, tail...) } - return append(head, tail...), nil + return encoded, nil } // Decode takes an ABI encoded byte string and a target ABI type, @@ -276,7 +278,7 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { fmt.Errorf("uint%d decode: expected byte length %d, but got byte length %d", valueType.bitSize, valueType.bitSize/8, len(valueByte)) } - uintValue := big.NewInt(0).SetBytes(valueByte) + uintValue := new(big.Int).SetBytes(valueByte) return MakeUint(uintValue, valueType.bitSize) case Ufixed: if len(valueByte) != int(valueType.bitSize)/8 { @@ -284,7 +286,7 @@ func Decode(valueByte []byte, valueType Type) (Value, error) { fmt.Errorf("ufixed%dx%d decode: expected length %d, got byte length %d", valueType.bitSize, valueType.precision, valueType.bitSize/8, len(valueByte)) } - ufixedNumerator := big.NewInt(0).SetBytes(valueByte) + ufixedNumerator := new(big.Int).SetBytes(valueByte) return MakeUfixed(ufixedNumerator, valueType.bitSize, valueType.precision) case Bool: if len(valueByte) != 1 { diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go index 2fed2d7b85..22d37c5531 100644 --- a/data/abi/abi_encode_test.go +++ b/data/abi/abi_encode_test.go @@ -513,9 +513,7 @@ func TestDecodeValid(t *testing.T) { inputUint := []uint64{1, 2, 3, 4, 5, 6, 7, 8} arrayElems := make([]Value, len(inputUint)) for index, uintVal := range inputUint { - temp, err := MakeUint64(uintVal) - require.NoError(t, err, "make uint64 should not return error") - arrayElems[index] = temp + arrayElems[index] = MakeUint64(uintVal) } uintT, err := MakeUintType(64) require.NoError(t, err, "make uint64 type should not return error") diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index b98a917a82..9770bf4fbc 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -111,37 +111,22 @@ func (t Type) String() string { var staticArrayRegexp *regexp.Regexp = nil var ufixedRegexp *regexp.Regexp = nil -var regexpInitialized = false -func initRegexps() (err error) { - if regexpInitialized { - return 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 { - return + panic(err.Error()) } ufixedRegexp, err = regexp.Compile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`) if err != nil { - return + panic(err.Error()) } - regexpInitialized = true - return } // TypeFromString de-serialize ABI type from a string following ABI encoding. func TypeFromString(str string) (Type, error) { - // if the regexps for type str matching are not initialized - // attempt to initialize with `initRegexps` - // if error return err - if !regexpInitialized { - err := initRegexps() - if err != nil { - return Type{}, err - } - } - switch { case strings.HasSuffix(str, "[]"): arrayArgType, err := TypeFromString(str[:len(str)-2]) @@ -391,20 +376,22 @@ func MakeTupleType(argumentTypes []Type) (Type, error) { func (t Type) Equal(t0 Type) bool { if t.abiTypeID != t0.abiTypeID { return false - } else if t.precision != t0.precision || t.bitSize != t0.bitSize { + } + if t.precision != t0.precision || t.bitSize != t0.bitSize { return false - } else if t.staticLength != t0.staticLength { + } + if t.staticLength != t0.staticLength { return false - } else { - if len(t.childTypes) != len(t0.childTypes) { + } + 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 } - for i := 0; i < len(t.childTypes); i++ { - if !t.childTypes[i].Equal(t0.childTypes[i]) { - return false - } - } } + return true } diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index 471ff54f48..d01dc93bdf 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -32,7 +32,8 @@ func TestMakeTypeValid(t *testing.T) { partitiontest.PartitionTest(t) // uint for i := 8; i <= 512; i += 8 { - uintType, _ := MakeUintType(uint16(i)) + 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) @@ -40,7 +41,8 @@ func TestMakeTypeValid(t *testing.T) { // ufixed for i := 8; i <= 512; i += 8 { for j := 1; j <= 160; j++ { - ufixedType, _ := MakeUfixedType(uint16(i), uint16(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, @@ -97,7 +99,7 @@ func TestMakeTypeValid(t *testing.T) { ), uint16(256), ), - testType: "dynamic array", + testType: "static array", expected: "bool[128][256]", }, // tuple type @@ -165,7 +167,7 @@ func TestMakeTypeInvalid(t *testing.T) { randPrecision = rand.Uint32() } _, err := MakeUfixedType(uint16(randSize), uint16(randPrecision)) - require.Error(t, err, "MakeUintType: should throw error on bitSize input %d", randSize) + require.Error(t, err, "MakeUfixedType: should throw error on bitSize %d, precision %d", randSize, randPrecision) } } @@ -173,7 +175,8 @@ func TestTypeFromStringValid(t *testing.T) { partitiontest.PartitionTest(t) // uint for i := 8; i <= 512; i += 8 { - expected, _ := MakeUintType(uint16(i)) + 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, @@ -182,7 +185,8 @@ func TestTypeFromStringValid(t *testing.T) { // ufixed for i := 8; i <= 512; i += 8 { for j := 1; j <= 160; j++ { - expected, _ := MakeUfixedType(uint16(i), uint16(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, @@ -483,7 +487,7 @@ func TestTypeMISC(t *testing.T) { 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") + require.NoError(t, err, "make ufixed type error: bitSize %d, precision %d", i, j) testpool = append(testpool, ufixedT) } } @@ -560,6 +564,7 @@ func TestTypeMISC(t *testing.T) { 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++ { diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go index 7091c50813..893bd6ab1c 100644 --- a/data/abi/abi_value.go +++ b/data/abi/abi_value.go @@ -18,6 +18,7 @@ package abi import ( "fmt" + "math" "math/big" ) @@ -28,27 +29,31 @@ type Value struct { } // MakeUint8 takes a go `uint8` and gives an ABI Value of ABI type `uint8`. -func MakeUint8(value uint8) (Value, error) { +func MakeUint8(value uint8) Value { bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 8) + 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, error) { +func MakeUint16(value uint16) Value { bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 16) + 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, error) { +func MakeUint32(value uint32) Value { bigInt := big.NewInt(int64(value)) - return MakeUint(bigInt, 32) + 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, error) { - bigInt := big.NewInt(int64(0)).SetUint64(value) - return MakeUint(bigInt, 64) +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, @@ -58,7 +63,7 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { if err != nil { return Value{}, err } - upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + 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) } @@ -68,7 +73,7 @@ func MakeUint(value *big.Int, size uint16) (Value, error) { }, nil } -// MakeUfixed takes a big rational number representation, a type bitSize, and a type precision, +// 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) @@ -110,12 +115,12 @@ func MakeAddress(value [32]byte) 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) >= (1 << 16) { - return Value{}, fmt.Errorf("dynamic array make error: pass in argument number larger than 2^16") + 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", + return Value{}, fmt.Errorf("type unmatch: %s and %s", values[i].ABIType.String(), elemType.String()) } } @@ -127,14 +132,14 @@ func MakeDynamicArray(values []Value, elemType Type) (Value, error) { // MakeStaticArray takes an array of ABI value and returns an ABI static length array value. func MakeStaticArray(values []Value) (Value, error) { - if len(values) >= (1 << 16) { - return Value{}, fmt.Errorf("static array make error: pass in argument number larger than 2^16") + 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 argument passed in") + 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", + return Value{}, fmt.Errorf("type unmatch: %s and %s", values[i].ABIType.String(), values[0].ABIType.String()) } } @@ -146,8 +151,8 @@ func MakeStaticArray(values []Value) (Value, error) { // MakeTuple takes an array of ABI values and returns an ABI tuple value. func MakeTuple(values []Value) (Value, error) { - if len(values) >= (1 << 16) { - return Value{}, fmt.Errorf("tuple make error: pass in argument number larger than 2^16") + 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++ { @@ -173,9 +178,17 @@ func MakeBool(value bool) Value { } } +func checkUintValid(t Type, bitSize uint16) bool { + if t.abiTypeID != Uint || t.bitSize > bitSize { + return false + } else { + return true + } +} + // GetUint8 tries to retreve an uint8 from an ABI Value. func (v Value) GetUint8() (uint8, error) { - if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 8 { + if checkUintValid(v.ABIType, 8) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -187,7 +200,7 @@ func (v Value) GetUint8() (uint8, error) { // GetUint16 tries to retrieve an uint16 from an ABI Value. func (v Value) GetUint16() (uint16, error) { - if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 16 { + if checkUintValid(v.ABIType, 16) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -199,7 +212,7 @@ func (v Value) GetUint16() (uint16, error) { // GetUint32 tries to retrieve an uint32 from an ABI Value. func (v Value) GetUint32() (uint32, error) { - if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 32 { + if checkUintValid(v.ABIType, 32) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -211,7 +224,7 @@ func (v Value) GetUint32() (uint32, error) { // GetUint64 tries to retrieve an uint64 from an ABI Value. func (v Value) GetUint64() (uint64, error) { - if v.ABIType.abiTypeID != Uint || v.ABIType.bitSize > 64 { + if checkUintValid(v.ABIType, 16) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -227,22 +240,22 @@ func (v Value) GetUint() (*big.Int, error) { return nil, fmt.Errorf("value type unmatch") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) + sizeThreshold := new(big.Int).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than uint bitSize") + return nil, fmt.Errorf("value exceeds uint bitSize scope") } return bigIntForm, nil } -// GetUfixed tries to retrieve an big rational number from an ABI Value. +// 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 unmatch, should be ufixed") } bigIntForm := v.value.(*big.Int) - sizeThreshold := big.NewInt(0).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) + sizeThreshold := new(big.Int).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) if sizeThreshold.Cmp(bigIntForm) <= 0 { - return nil, fmt.Errorf("value is larger than ufixed bitSize") + return nil, fmt.Errorf("value exceeds ufixed bitSize scope") } return bigIntForm, nil } From da39a1f88ff265687e09f60315550970cc1210ac Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 15:45:16 -0400 Subject: [PATCH 25/29] resolve review comments --- data/abi/abi_value.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go index 893bd6ab1c..3d203231cb 100644 --- a/data/abi/abi_value.go +++ b/data/abi/abi_value.go @@ -179,16 +179,12 @@ func MakeBool(value bool) Value { } func checkUintValid(t Type, bitSize uint16) bool { - if t.abiTypeID != Uint || t.bitSize > bitSize { - return false - } else { - return true - } + 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) { + if !checkUintValid(v.ABIType, 8) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -200,7 +196,7 @@ func (v Value) GetUint8() (uint8, error) { // GetUint16 tries to retrieve an uint16 from an ABI Value. func (v Value) GetUint16() (uint16, error) { - if checkUintValid(v.ABIType, 16) { + if !checkUintValid(v.ABIType, 16) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -212,7 +208,7 @@ func (v Value) GetUint16() (uint16, error) { // GetUint32 tries to retrieve an uint32 from an ABI Value. func (v Value) GetUint32() (uint32, error) { - if checkUintValid(v.ABIType, 32) { + if !checkUintValid(v.ABIType, 32) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() @@ -224,7 +220,7 @@ func (v Value) GetUint32() (uint32, error) { // GetUint64 tries to retrieve an uint64 from an ABI Value. func (v Value) GetUint64() (uint64, error) { - if checkUintValid(v.ABIType, 16) { + if !checkUintValid(v.ABIType, 64) { return 0, fmt.Errorf("value type unmatch or bitSize too large") } bigIntForm, err := v.GetUint() From 1ce16fd28cbdd2ed0da3240261231b62be1a4e73 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 17:30:28 -0400 Subject: [PATCH 26/29] minor --- data/abi/abi_type.go | 5 +++-- data/abi/abi_type_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index 9770bf4fbc..9ec0f93256 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -18,6 +18,7 @@ package abi import ( "fmt" + "math" "regexp" "strconv" "strings" @@ -362,8 +363,8 @@ func MakeStringType() Type { // MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument. func MakeTupleType(argumentTypes []Type) (Type, error) { - if len(argumentTypes) >= (1 << 16) { - return Type{}, fmt.Errorf("tuple type child type number >= 2^16 error") + if len(argumentTypes) >= math.MaxUint16 { + return Type{}, fmt.Errorf("tuple type child type number larger than maximum uint16 error") } return Type{ abiTypeID: Tuple, diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go index d01dc93bdf..136ecb8cf7 100644 --- a/data/abi/abi_type_test.go +++ b/data/abi/abi_type_test.go @@ -88,7 +88,7 @@ func TestMakeTypeValid(t *testing.T) { }, uint16(100), ), - testType: "dynamic array", + testType: "static array", expected: "ufixed128x10[100]", }, { From 023e10d274fa7317caefbae1e810f6c89fa1bcbd Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 17:32:14 -0400 Subject: [PATCH 27/29] minor --- data/abi/abi_value.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/data/abi/abi_value.go b/data/abi/abi_value.go index 3d203231cb..9f72ba755d 100644 --- a/data/abi/abi_value.go +++ b/data/abi/abi_value.go @@ -120,7 +120,7 @@ func MakeDynamicArray(values []Value, elemType Type) (Value, error) { } for i := 0; i < len(values); i++ { if !values[i].ABIType.Equal(elemType) { - return Value{}, fmt.Errorf("type unmatch: %s and %s", + return Value{}, fmt.Errorf("type mismatch: %s and %s", values[i].ABIType.String(), elemType.String()) } } @@ -139,7 +139,7 @@ func MakeStaticArray(values []Value) (Value, error) { } for i := 0; i < len(values); i++ { if !values[i].ABIType.Equal(values[0].ABIType) { - return Value{}, fmt.Errorf("type unmatch: %s and %s", + return Value{}, fmt.Errorf("type mismatch: %s and %s", values[i].ABIType.String(), values[0].ABIType.String()) } } @@ -185,7 +185,7 @@ func checkUintValid(t Type, bitSize uint16) bool { // 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 unmatch or bitSize too large") + return 0, fmt.Errorf("value type mismatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -197,7 +197,7 @@ func (v Value) GetUint8() (uint8, error) { // 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 unmatch or bitSize too large") + return 0, fmt.Errorf("value type mismatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -209,7 +209,7 @@ func (v Value) GetUint16() (uint16, error) { // 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 unmatch or bitSize too large") + return 0, fmt.Errorf("value type mismatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -221,7 +221,7 @@ func (v Value) GetUint32() (uint32, error) { // 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 unmatch or bitSize too large") + return 0, fmt.Errorf("value type mismatch or bitSize too large") } bigIntForm, err := v.GetUint() if err != nil { @@ -233,7 +233,7 @@ func (v Value) GetUint64() (uint64, error) { // 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 unmatch") + return nil, fmt.Errorf("value type mismatch") } bigIntForm := v.value.(*big.Int) sizeThreshold := new(big.Int).Lsh(big.NewInt(1), uint(v.ABIType.bitSize)) @@ -246,7 +246,7 @@ func (v Value) GetUint() (*big.Int, error) { // 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 unmatch, should be 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)) @@ -259,7 +259,7 @@ func (v Value) GetUfixed() (*big.Int, error) { // 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 unmatch, should be ufixed") + return "", fmt.Errorf("value type mismatch, should be ufixed") } stringForm := v.value.(string) return stringForm, nil @@ -268,7 +268,7 @@ func (v Value) GetString() (string, error) { // 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 unmatch, should be bytes") + return byte(0), fmt.Errorf("value type mismatch, should be bytes") } bytesForm := v.value.(byte) return bytesForm, nil @@ -277,7 +277,7 @@ func (v Value) GetByte() (byte, error) { // 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 unmatch, should be address") + return [32]byte{}, fmt.Errorf("value type mismatch, should be address") } addressForm := v.value.([32]byte) return addressForm, nil @@ -306,7 +306,7 @@ func (v Value) GetValueByIndex(index uint16) (Value, error) { // 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 unmatch, should be bool") + return false, fmt.Errorf("value type mismatch, should be bool") } boolForm := v.value.(bool) return boolForm, nil From 1aaf43d50fa61e2dc998fc99a6d917e1a44a0ac9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 22 Sep 2021 18:09:55 -0400 Subject: [PATCH 28/29] update encode slot capacity --- data/abi/abi_encode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index 0452bbd082..db4750bd00 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -258,7 +258,7 @@ func tupleEncoding(v Value) ([]byte, error) { } // concat everything as the abi encoded bytes - encoded := make([]byte, 0) + encoded := make([]byte, 0, headLength+tailCurrLength) for _, head := range heads { encoded = append(encoded, head...) } From 5b60ae64a55cd4e566be87d0485406b59f245095 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 24 Sep 2021 13:43:51 -0400 Subject: [PATCH 29/29] minor --- data/abi/abi_type.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index 9ec0f93256..65e3dfc6af 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -223,9 +223,7 @@ func parseTupleContent(str string) ([]string, error) { // str should noe have leading/tailing comma if strings.HasSuffix(str, ",") || strings.HasPrefix(str, ",") { - return []string{}, - fmt.Errorf("parsing error: cannot replace tuple segment back: " + - "number of empty placeholders do not match with number of sub-tuples") + return []string{}, fmt.Errorf("parsing error: tuple content should not start with comma") } // str should not have consecutive commas contained