diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 884c3d6e68..77596f38a3 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -23,6 +23,7 @@ import ( "encoding/base64" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" "net/http" @@ -32,9 +33,9 @@ import ( "github.com/spf13/cobra" + "github.com/algorand/avm-abi/abi" "github.com/algorand/go-algorand/crypto" apiclient "github.com/algorand/go-algorand/daemon/algod/api/client" - "github.com/algorand/go-algorand/data/abi" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -1169,6 +1170,76 @@ func populateMethodCallReferenceArgs(sender string, currentApp uint64, types []s return resolvedIndexes, nil } +// maxAppArgs is the maximum number of arguments for an application call transaction, in compliance +// with ARC-4. Currently this is the same as the MaxAppArgs consensus parameter, but the +// difference is that the consensus parameter is liable to change in a future consensus upgrade. +// However, the ARC-4 ABI argument encoding **MUST** always remain the same. +const maxAppArgs = 16 + +// The tuple threshold is maxAppArgs, minus 1 for the method selector in the first app arg, +// minus 1 for the final app argument becoming a tuple of the remaining method args +const methodArgsTupleThreshold = maxAppArgs - 2 + +// parseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes +// it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format) +// if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple +func parseMethodArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error { + abiTypes := make([]abi.Type, len(argTypes)) + for i, typeString := range argTypes { + abiType, err := abi.TypeOf(typeString) + if err != nil { + return err + } + abiTypes[i] = abiType + } + + if len(abiTypes) != len(jsonArgs) { + return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTypes)) + } + + // Up to 16 app arguments can be passed to app call. First is reserved for method selector, + // and the rest are for method call arguments. But if more than 15 method call arguments + // are present, then the method arguments after the 14th are placed in a tuple in the last + // app argument slot + if len(abiTypes) > maxAppArgs-1 { + typesForTuple := make([]abi.Type, len(abiTypes)-methodArgsTupleThreshold) + copy(typesForTuple, abiTypes[methodArgsTupleThreshold:]) + + compactedType, err := abi.MakeTupleType(typesForTuple) + if err != nil { + return err + } + + abiTypes = append(abiTypes[:methodArgsTupleThreshold], compactedType) + + tupleValues := make([]json.RawMessage, len(jsonArgs)-methodArgsTupleThreshold) + for i, jsonArg := range jsonArgs[methodArgsTupleThreshold:] { + tupleValues[i] = []byte(jsonArg) + } + + remainingJSON, err := json.Marshal(tupleValues) + if err != nil { + return err + } + + jsonArgs = append(jsonArgs[:methodArgsTupleThreshold], string(remainingJSON)) + } + + // parse JSON value to ABI encoded bytes + for i := 0; i < len(jsonArgs); i++ { + interfaceVal, err := abiTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i])) + if err != nil { + return err + } + abiEncoded, err := abiTypes[i].Encode(interfaceVal) + if err != nil { + return err + } + *applicationArgs = append(*applicationArgs, abiEncoded) + } + return nil +} + var methodAppCmd = &cobra.Command{ Use: "method", Short: "Invoke an ABI method", @@ -1284,7 +1355,7 @@ var methodAppCmd = &cobra.Command{ basicArgValues[basicArgIndex] = strconv.Itoa(resolved) } - err = abi.ParseArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs) + err = parseMethodArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs) if err != nil { reportErrorf("cannot parse arguments to ABI encoding: %v", err) } diff --git a/cmd/goal/application_test.go b/cmd/goal/application_test.go new file mode 100644 index 0000000000..7de23a5be0 --- /dev/null +++ b/cmd/goal/application_test.go @@ -0,0 +1,143 @@ +// Copyright (C) 2019-2022 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 main + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestParseMethodArgJSONtoByteSlice(t *testing.T) { + partitiontest.PartitionTest(t) + + makeRepeatSlice := func(size int, value string) []string { + slice := make([]string, size) + for i := range slice { + slice[i] = value + } + return slice + } + + tests := []struct { + argTypes []string + jsonArgs []string + expectedAppArgs [][]byte + }{ + { + argTypes: []string{}, + jsonArgs: []string{}, + expectedAppArgs: [][]byte{}, + }, + { + argTypes: []string{"uint8"}, + jsonArgs: []string{"100"}, + expectedAppArgs: [][]byte{{100}}, + }, + { + argTypes: []string{"uint8", "uint16"}, + jsonArgs: []string{"100", "65535"}, + expectedAppArgs: [][]byte{{100}, {255, 255}}, + }, + { + argTypes: makeRepeatSlice(15, "string"), + jsonArgs: []string{ + `"a"`, + `"b"`, + `"c"`, + `"d"`, + `"e"`, + `"f"`, + `"g"`, + `"h"`, + `"i"`, + `"j"`, + `"k"`, + `"l"`, + `"m"`, + `"n"`, + `"o"`, + }, + expectedAppArgs: [][]byte{ + {00, 01, 97}, + {00, 01, 98}, + {00, 01, 99}, + {00, 01, 100}, + {00, 01, 101}, + {00, 01, 102}, + {00, 01, 103}, + {00, 01, 104}, + {00, 01, 105}, + {00, 01, 106}, + {00, 01, 107}, + {00, 01, 108}, + {00, 01, 109}, + {00, 01, 110}, + {00, 01, 111}, + }, + }, + { + argTypes: makeRepeatSlice(16, "string"), + jsonArgs: []string{ + `"a"`, + `"b"`, + `"c"`, + `"d"`, + `"e"`, + `"f"`, + `"g"`, + `"h"`, + `"i"`, + `"j"`, + `"k"`, + `"l"`, + `"m"`, + `"n"`, + `"o"`, + `"p"`, + }, + expectedAppArgs: [][]byte{ + {00, 01, 97}, + {00, 01, 98}, + {00, 01, 99}, + {00, 01, 100}, + {00, 01, 101}, + {00, 01, 102}, + {00, 01, 103}, + {00, 01, 104}, + {00, 01, 105}, + {00, 01, 106}, + {00, 01, 107}, + {00, 01, 108}, + {00, 01, 109}, + {00, 01, 110}, + {00, 04, 00, 07, 00, 01, 111, 00, 01, 112}, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("index=%d", i), func(t *testing.T) { + applicationArgs := [][]byte{} + err := parseMethodArgJSONtoByteSlice(test.argTypes, test.jsonArgs, &applicationArgs) + require.NoError(t, err) + require.Equal(t, test.expectedAppArgs, applicationArgs) + }) + } +} diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go deleted file mode 100644 index b0f8b6c12e..0000000000 --- a/data/abi/abi_encode.go +++ /dev/null @@ -1,617 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package abi - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "math/big" - "reflect" - "strings" -) - -// typeCastToTuple cast an array-like ABI type into an ABI tuple type. -func (t Type) typeCastToTuple(tupLen ...int) (Type, error) { - var childT []Type - - switch t.abiTypeID { - case String: - if len(tupLen) != 1 { - return Type{}, fmt.Errorf("string type conversion to tuple need 1 length argument") - } - childT = make([]Type, tupLen[0]) - for i := 0; i < tupLen[0]; i++ { - childT[i] = byteType - } - case Address: - childT = make([]Type, addressByteSize) - for i := 0; i < addressByteSize; i++ { - childT[i] = byteType - } - case ArrayStatic: - childT = make([]Type, t.staticLength) - for i := 0; i < int(t.staticLength); i++ { - childT[i] = t.childTypes[0] - } - case ArrayDynamic: - if len(tupLen) != 1 { - return Type{}, fmt.Errorf("dynamic array type conversion to tuple need 1 length argument") - } - childT = make([]Type, tupLen[0]) - for i := 0; i < tupLen[0]; i++ { - childT[i] = t.childTypes[0] - } - default: - return Type{}, fmt.Errorf("type cannot support conversion to tuple") - } - - tuple, err := MakeTupleType(childT) - if err != nil { - return Type{}, err - } - return tuple, nil -} - -// Encode is an ABI type method to encode go values into bytes following ABI encoding rules -func (t Type) Encode(value interface{}) ([]byte, error) { - switch t.abiTypeID { - case Uint, Ufixed: - return encodeInt(value, t.bitSize) - case Bool: - boolValue, ok := value.(bool) - if !ok { - return nil, fmt.Errorf("cannot cast value to bool in bool encoding") - } - if boolValue { - return []byte{0x80}, nil - } - return []byte{0x00}, nil - case Byte: - byteValue, ok := value.(byte) - if !ok { - return nil, fmt.Errorf("cannot cast value to byte in byte encoding") - } - return []byte{byteValue}, nil - case ArrayStatic, Address: - castedType, err := t.typeCastToTuple() - if err != nil { - return nil, err - } - return castedType.Encode(value) - case ArrayDynamic: - dynamicArray, err := inferToSlice(value) - if err != nil { - return nil, err - } - castedType, err := t.typeCastToTuple(len(dynamicArray)) - if err != nil { - return nil, err - } - lengthEncode := make([]byte, lengthEncodeByteSize) - binary.BigEndian.PutUint16(lengthEncode, uint16(len(dynamicArray))) - encoded, err := castedType.Encode(value) - if err != nil { - return nil, err - } - encoded = append(lengthEncode, encoded...) - return encoded, nil - case String: - stringValue, okString := value.(string) - if !okString { - return nil, fmt.Errorf("cannot cast value to string or array dynamic in encoding") - } - byteValue := []byte(stringValue) - castedType, err := t.typeCastToTuple(len(byteValue)) - if err != nil { - return nil, err - } - lengthEncode := make([]byte, lengthEncodeByteSize) - binary.BigEndian.PutUint16(lengthEncode, uint16(len(byteValue))) - encoded, err := castedType.Encode(byteValue) - if err != nil { - return nil, err - } - encoded = append(lengthEncode, encoded...) - return encoded, nil - case Tuple: - return encodeTuple(value, t.childTypes) - default: - return nil, fmt.Errorf("cannot infer type for encoding") - } -} - -// encodeInt encodes int-alike golang values to bytes, following ABI encoding rules -func encodeInt(intValue interface{}, bitSize uint16) ([]byte, error) { - var bigInt *big.Int - - switch intValue := intValue.(type) { - case int8: - bigInt = big.NewInt(int64(intValue)) - case uint8: - bigInt = new(big.Int).SetUint64(uint64(intValue)) - case int16: - bigInt = big.NewInt(int64(intValue)) - case uint16: - bigInt = new(big.Int).SetUint64(uint64(intValue)) - case int32: - bigInt = big.NewInt(int64(intValue)) - case uint32: - bigInt = new(big.Int).SetUint64(uint64(intValue)) - case int64: - bigInt = big.NewInt(intValue) - case uint64: - bigInt = new(big.Int).SetUint64(intValue) - case uint: - bigInt = new(big.Int).SetUint64(uint64(intValue)) - case int: - bigInt = big.NewInt(int64(intValue)) - case *big.Int: - bigInt = intValue - default: - return nil, fmt.Errorf("cannot infer go type for uint encode") - } - - if bigInt.Sign() < 0 { - return nil, fmt.Errorf("passed in numeric value should be non negative") - } - - castedBytes := make([]byte, bitSize/8) - - if bigInt.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bitSize))) >= 0 { - return nil, fmt.Errorf("input value bit size %d > abi type bit size %d", bigInt.BitLen(), bitSize) - } - - bigInt.FillBytes(castedBytes) - return castedBytes, nil -} - -// inferToSlice infers an interface element to a slice of interface{}, returns error if it cannot infer successfully -func inferToSlice(value interface{}) ([]interface{}, error) { - reflectVal := reflect.ValueOf(value) - if reflectVal.Kind() != reflect.Slice && reflectVal.Kind() != reflect.Array { - return nil, fmt.Errorf("cannot infer an interface value as a slice of interface element") - } - // * if input is a slice, with nil, then reflectVal.Len() == 0 - // * if input is an array, it is not possible it is nil - values := make([]interface{}, reflectVal.Len()) - for i := 0; i < reflectVal.Len(); i++ { - values[i] = reflectVal.Index(i).Interface() - } - return values, nil -} - -// encodeTuple encodes slice-of-interface of golang values to bytes, following ABI encoding rules -func encodeTuple(value interface{}, childT []Type) ([]byte, error) { - if len(childT) >= abiEncodingLengthLimit { - return nil, fmt.Errorf("abi child type number exceeds uint16 maximum") - } - values, err := inferToSlice(value) - if err != nil { - return nil, err - } - if len(values) != len(childT) { - return nil, fmt.Errorf("cannot encode abi tuple: value slice length != child type 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(childT)) - tails := make([][]byte, len(childT)) - isDynamicIndex := make(map[int]bool) - - for i := 0; i < len(childT); i++ { - if childT[i].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 := childT[i].Encode(values[i]) - if err != nil { - return nil, err - } - tails[i] = tailEncoding - isDynamicIndex[i] = true - } else if childT[i].abiTypeID == Bool { - // search previous bool - before := findBoolLR(childT, i, -1) - // search after bool - after := findBoolLR(childT, i, 1) - // append to heads and tails - if before%8 != 0 { - return nil, fmt.Errorf("cannot encode abi tuple: expected before has number of bool mod 8 == 0") - } - if after > 7 { - after = 7 - } - compressed, err := compressBools(values[i : i+after+1]) - if err != nil { - return nil, err - } - heads[i] = []byte{compressed} - i += after - isDynamicIndex[i] = false - } else { - encodeTi, err := childT[i].Encode(values[i]) - if err != nil { - return nil, err - } - heads[i] = encodeTi - isDynamicIndex[i] = false - } - } - - // adjust heads for dynamic type - // since head size can be pre-determined (for we are storing static value and dynamic value index in head) - // we accumulate the head size first - // (also note that though head size is pre-determined, head value is not necessarily pre-determined) - headLength := 0 - for _, headTi := range heads { - headLength += len(headTi) - } - - // when we iterate through the heads (byte slice), we need to find heads for dynamic values - // the head should correspond to the start index: len( head(x[1]) ... head(x[N]) tail(x[1]) ... tail(x[i-1]) ). - tailCurrLength := 0 - for i := 0; i < len(heads); i++ { - if isDynamicIndex[i] { - // calculate where the index of dynamic value encoding byte start - headValue := headLength + tailCurrLength - if headValue >= abiEncodingLengthLimit { - return nil, fmt.Errorf("cannot encode abi tuple: encode length exceeds uint16 maximum") - } - binary.BigEndian.PutUint16(heads[i], uint16(headValue)) - } - // accumulate the current tailing dynamic encoding bytes length. - tailCurrLength += len(tails[i]) - } - - // concat everything as the abi encoded bytes - encoded := make([]byte, 0, headLength+tailCurrLength) - for _, head := range heads { - encoded = append(encoded, head...) - } - for _, tail := range tails { - encoded = append(encoded, tail...) - } - return encoded, nil -} - -// compressBools takes a slice of interface{} (which can be casted to bools) length <= 8 -// and compress the bool values into a uint8 integer -func compressBools(boolSlice []interface{}) (uint8, error) { - var res uint8 = 0 - if len(boolSlice) > 8 { - return 0, fmt.Errorf("compressBools: cannot have slice length > 8") - } - for i := 0; i < len(boolSlice); i++ { - temp, ok := boolSlice[i].(bool) - if !ok { - return 0, fmt.Errorf("compressBools: cannot cast slice element to bool") - } - if temp { - res |= 1 << uint(7-i) - } - } - return res, nil -} - -// decodeUint decodes byte slice into golang int/big.Int -func decodeUint(encoded []byte, bitSize uint16) (interface{}, error) { - if len(encoded) != int(bitSize)/8 { - return nil, - fmt.Errorf("uint/ufixed decode: expected byte length %d, but got byte length %d", bitSize/8, len(encoded)) - } - switch bitSize / 8 { - case 1: - return encoded[0], nil - case 2: - return uint16(new(big.Int).SetBytes(encoded).Uint64()), nil - case 3, 4: - return uint32(new(big.Int).SetBytes(encoded).Uint64()), nil - case 5, 6, 7, 8: - return new(big.Int).SetBytes(encoded).Uint64(), nil - default: - return new(big.Int).SetBytes(encoded), nil - } -} - -// Decode is an ABI type method to decode bytes to go values from ABI encoding rules -func (t Type) Decode(encoded []byte) (interface{}, error) { - switch t.abiTypeID { - case Uint, Ufixed: - return decodeUint(encoded, t.bitSize) - case Bool: - if len(encoded) != 1 { - return nil, fmt.Errorf("boolean byte should be length 1 byte") - } - if encoded[0] == 0x00 { - return false, nil - } else if encoded[0] == 0x80 { - return true, nil - } - return nil, fmt.Errorf("single boolean encoded byte should be of form 0x80 or 0x00") - case Byte: - if len(encoded) != 1 { - return nil, fmt.Errorf("byte should be length 1") - } - return encoded[0], nil - case ArrayStatic: - castedType, err := t.typeCastToTuple() - if err != nil { - return nil, err - } - return castedType.Decode(encoded) - case Address: - if len(encoded) != addressByteSize { - return nil, fmt.Errorf("address should be length 32") - } - return encoded, nil - case ArrayDynamic: - if len(encoded) < lengthEncodeByteSize { - return nil, fmt.Errorf("dynamic array format corrupted") - } - dynamicLen := binary.BigEndian.Uint16(encoded[:lengthEncodeByteSize]) - castedType, err := t.typeCastToTuple(int(dynamicLen)) - if err != nil { - return nil, err - } - return castedType.Decode(encoded[lengthEncodeByteSize:]) - case String: - if len(encoded) < lengthEncodeByteSize { - return nil, fmt.Errorf("string format corrupted") - } - stringLenBytes := encoded[:lengthEncodeByteSize] - byteLen := binary.BigEndian.Uint16(stringLenBytes) - if len(encoded[lengthEncodeByteSize:]) != int(byteLen) { - return nil, fmt.Errorf("string representation in byte: length not matching") - } - return string(encoded[lengthEncodeByteSize:]), nil - case Tuple: - return decodeTuple(encoded, t.childTypes) - default: - return nil, fmt.Errorf("cannot infer type for decoding") - } -} - -// decodeTuple decodes byte slice with ABI type slice, outputting a slice of golang interface values -// following ABI encoding rules -func decodeTuple(encoded []byte, childT []Type) ([]interface{}, error) { - dynamicSegments := make([]int, 0, len(childT)+1) - valuePartition := make([][]byte, 0, len(childT)) - iterIndex := 0 - - for i := 0; i < len(childT); i++ { - if childT[i].IsDynamic() { - if len(encoded[iterIndex:]) < lengthEncodeByteSize { - return nil, fmt.Errorf("ill formed tuple dynamic typed value encoding") - } - dynamicIndex := binary.BigEndian.Uint16(encoded[iterIndex : iterIndex+lengthEncodeByteSize]) - dynamicSegments = append(dynamicSegments, int(dynamicIndex)) - valuePartition = append(valuePartition, nil) - iterIndex += lengthEncodeByteSize - } else if childT[i].abiTypeID == Bool { - // search previous bool - before := findBoolLR(childT, i, -1) - // search after bool - after := findBoolLR(childT, i, 1) - if before%8 == 0 { - if after > 7 { - after = 7 - } - // parse bool in a byte to multiple byte strings - for boolIndex := uint(0); boolIndex <= uint(after); boolIndex++ { - boolMask := 0x80 >> boolIndex - if encoded[iterIndex]&byte(boolMask) > 0 { - valuePartition = append(valuePartition, []byte{0x80}) - } else { - valuePartition = append(valuePartition, []byte{0x00}) - } - } - i += after - iterIndex++ - } else { - return nil, fmt.Errorf("expected before bool number mod 8 == 0") - } - } else { - // not bool ... - currLen, err := childT[i].ByteLen() - if err != nil { - return nil, err - } - valuePartition = append(valuePartition, encoded[iterIndex:iterIndex+currLen]) - iterIndex += currLen - } - if i != len(childT)-1 && iterIndex >= len(encoded) { - return nil, fmt.Errorf("input byte not enough to decode") - } - } - - if len(dynamicSegments) > 0 { - dynamicSegments = append(dynamicSegments, len(encoded)) - iterIndex = len(encoded) - } - if iterIndex < len(encoded) { - return nil, fmt.Errorf("input byte not fully consumed") - } - for i := 0; i < len(dynamicSegments)-1; i++ { - if dynamicSegments[i] > dynamicSegments[i+1] { - return nil, fmt.Errorf("dynamic segment should display a [l, r] space with l <= r") - } - } - - segIndex := 0 - for i := 0; i < len(childT); i++ { - if childT[i].IsDynamic() { - valuePartition[i] = encoded[dynamicSegments[segIndex]:dynamicSegments[segIndex+1]] - segIndex++ - } - } - - values := make([]interface{}, len(childT)) - for i := 0; i < len(childT); i++ { - var err error - values[i], err = childT[i].Decode(valuePartition[i]) - if err != nil { - return nil, err - } - } - return values, nil -} - -// maxAppArgs is the maximum number of arguments for an application call transaction, in compliance -// with ARC-4. Currently this is the same as the MaxAppArgs consensus parameter, but the -// difference is that the consensus parameter is liable to change in a future consensus upgrade. -// However, the ARC-4 ABI argument encoding **MUST** always remain the same. -const maxAppArgs = 16 - -// The tuple threshold is maxAppArgs, minus 1 for the method selector in the first app arg, -// minus 1 for the final app argument becoming a tuple of the remaining method args -const methodArgsTupleThreshold = maxAppArgs - 2 - -// ParseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes -// it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format) -// if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple -func ParseArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error { - abiTypes := make([]Type, len(argTypes)) - for i, typeString := range argTypes { - abiType, err := TypeOf(typeString) - if err != nil { - return err - } - abiTypes[i] = abiType - } - - if len(abiTypes) != len(jsonArgs) { - return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTypes)) - } - - // Up to 16 app arguments can be passed to app call. First is reserved for method selector, - // and the rest are for method call arguments. But if more than 15 method call arguments - // are present, then the method arguments after the 14th are placed in a tuple in the last - // app argument slot - if len(abiTypes) > maxAppArgs-1 { - typesForTuple := make([]Type, len(abiTypes)-methodArgsTupleThreshold) - copy(typesForTuple, abiTypes[methodArgsTupleThreshold:]) - - compactedType, err := MakeTupleType(typesForTuple) - if err != nil { - return err - } - - abiTypes = append(abiTypes[:methodArgsTupleThreshold], compactedType) - - tupleValues := make([]json.RawMessage, len(jsonArgs)-methodArgsTupleThreshold) - for i, jsonArg := range jsonArgs[methodArgsTupleThreshold:] { - tupleValues[i] = []byte(jsonArg) - } - - remainingJSON, err := json.Marshal(tupleValues) - if err != nil { - return err - } - - jsonArgs = append(jsonArgs[:methodArgsTupleThreshold], string(remainingJSON)) - } - - // parse JSON value to ABI encoded bytes - for i := 0; i < len(jsonArgs); i++ { - interfaceVal, err := abiTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i])) - if err != nil { - return err - } - abiEncoded, err := abiTypes[i].Encode(interfaceVal) - if err != nil { - return err - } - *applicationArgs = append(*applicationArgs, abiEncoded) - } - return nil -} - -// ParseMethodSignature parses a method of format `method(argType1,argType2,...)retType` -// into `method` {`argType1`,`argType2`,...} and `retType` -func ParseMethodSignature(methodSig string) (name string, argTypes []string, returnType string, err error) { - argsStart := strings.Index(methodSig, "(") - if argsStart == -1 { - err = fmt.Errorf(`No parenthesis in method signature: "%s"`, methodSig) - return - } - - if argsStart == 0 { - err = fmt.Errorf(`Method signature has no name: "%s"`, methodSig) - return - } - - argsEnd := -1 - depth := 0 - for index, char := range methodSig { - if char == '(' { - depth++ - } else if char == ')' { - if depth == 0 { - err = fmt.Errorf(`Unpaired parenthesis in method signature: "%s"`, methodSig) - return - } - depth-- - if depth == 0 { - argsEnd = index - break - } - } - } - - if argsEnd == -1 { - err = fmt.Errorf(`Unpaired parenthesis in method signature: "%s"`, methodSig) - return - } - - name = methodSig[:argsStart] - argTypes, err = parseTupleContent(methodSig[argsStart+1 : argsEnd]) - returnType = methodSig[argsEnd+1:] - return -} - -// VerifyMethodSignature checks if a method signature and its referenced types can be parsed properly -func VerifyMethodSignature(methodSig string) error { - _, argTypes, retType, err := ParseMethodSignature(methodSig) - if err != nil { - return err - } - - for i, argType := range argTypes { - if IsReferenceType(argType) || IsTransactionType(argType) { - continue - } - - _, err = TypeOf(argType) - if err != nil { - return fmt.Errorf("Error parsing argument type at index %d: %s", i, err.Error()) - } - } - - if retType != VoidReturnType { - _, err = TypeOf(retType) - if err != nil { - return fmt.Errorf("Error parsing return type: %s", err.Error()) - } - } - - return nil -} diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go deleted file mode 100644 index 231c1a0e0a..0000000000 --- a/data/abi/abi_encode_test.go +++ /dev/null @@ -1,1279 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package abi - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "math/big" - "testing" - - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/chrismcguire/gobberish" - "github.com/stretchr/testify/require" -) - -const ( - uintStepLength = 8 - uintBegin = 8 - uintEnd = 512 - uintRandomTestPoints = 1000 - uintTestCaseCount = 200 - ufixedPrecision = 160 - ufixedRandomTestPoints = 20 - tupleMaxLength = 10 - byteTestCaseCount = 1 << 8 - boolTestCaseCount = 2 - addressTestCaseCount = 300 - stringTestCaseCount = 10 - stringTestCaseSpecLenCount = 5 - takeNum = 10 - tupleTestCaseCount = 100 -) - -/* - The set of parameters ensure that the error of byte length >= 2^16 is eliminated. - - i. Consider uint512[] with length 10, the ABI encoding length is: 64 x 10 + 2 - (2 is introduced from dynamic array length encoding) - The motivation here is that, forall ABI type that is non-array/non-tuple like, - uint512 gives the longest byte length in ABI encoding - (utf-8 string's byte length is at most 42, address byte length is at most 32) - - ii. Consider a tuple of length 10, with all elements uint512[] of length 10. - The ABI encoding length is: 10 x 2 + 10 x 642 == 6440 - (2 is for tuple index to keep track of dynamic type encoding) - - iii. Consider a tuple of length 10, with all elements of tuples mentioned in (ii). - The ABI encoding length is: 10 x 2 + 10 x 6440 == 64420 - This is the end of the generation of nested-tuple test case, - no more layers of random tuples will be produced. - - This gives an upper bound for the produced ABI encoding byte length in this test script, - and noticing that length 64420 mentioned in (iii) is less than 2^16 == 65536. - Assuming that ABI implementation is correct, then the flaky test should not happen again. -*/ - -func TestEncodeValid(t *testing.T) { - partitiontest.PartitionTest(t) - - // encoding test for uint type, iterating through all uint sizes - // randomly pick 1000 valid uint values and check if encoded value match with expected - for intSize := uintBegin; intSize <= uintEnd; intSize += uintStepLength { - upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(intSize)) - uintType, err := makeUintType(intSize) - require.NoError(t, err, "make uint type fail") - - for i := 0; i < uintRandomTestPoints; i++ { - randomInt, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - expected := make([]byte, intSize/8) - randomInt.FillBytes(expected) - - uintEncode, err := uintType.Encode(randomInt) - require.NoError(t, err, "encoding from uint type fail") - - require.Equal(t, expected, uintEncode, "encode uint not match with expected") - } - // 2^[bitSize] - 1 test - // check if uint can contain max uint value (2^bitSize - 1) - largest := new(big.Int).Add( - upperLimit, - new(big.Int).Neg(big.NewInt(1)), - ) - encoded, err := uintType.Encode(largest) - require.NoError(t, err, "largest uint encode error") - require.Equal(t, largest.Bytes(), encoded, "encode uint largest do not match with expected") - } - - // encoding test for ufixed, iterating through all the valid ufixed bitSize and precision - // randomly generate 10 big int values for ufixed numerator and check if encoded value match with expected - // also check if ufixed can fit max numerator (2^bitSize - 1) under specific byte bitSize - for size := uintBegin; size <= uintEnd; size += uintStepLength { - upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(size)) - largest := big.NewInt(0).Add( - upperLimit, - new(big.Int).Neg(big.NewInt(1)), - ) - for precision := 1; precision <= ufixedPrecision; precision++ { - typeUfixed, err := makeUfixedType(size, precision) - require.NoError(t, err, "make ufixed type fail") - - for i := 0; i < ufixedRandomTestPoints; i++ { - randomInt, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - encodedUfixed, err := typeUfixed.Encode(randomInt) - require.NoError(t, err, "ufixed encode fail") - - expected := make([]byte, size/8) - randomInt.FillBytes(expected) - require.Equal(t, expected, encodedUfixed, "encode ufixed not match with expected") - } - // (2^[bitSize] - 1) / (10^[precision]) test - ufixedLargestEncode, err := typeUfixed.Encode(largest) - require.NoError(t, err, "largest ufixed encode error") - require.Equal(t, largest.Bytes(), ufixedLargestEncode, - "encode ufixed largest do not match with expected") - } - } - - // encoding test for address, since address is 32 byte, it can be considered as 256 bit uint - // randomly generate 1000 uint256 and make address values, check if encoded value match with expected - upperLimit := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3) - for i := 0; i < uintRandomTestPoints; i++ { - randomAddrInt, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - addrBytesExpected := make([]byte, addressByteSize) - randomAddrInt.FillBytes(addrBytesExpected) - - addrBytesActual, err := addressType.Encode(addrBytesExpected) - require.NoError(t, err, "address encode fail") - require.Equal(t, addrBytesExpected, addrBytesActual, "encode addr not match with expected") - } - - // encoding test for bool values - for i := 0; i < boolTestCaseCount; i++ { - boolEncode, err := boolType.Encode(i == 1) - require.NoError(t, err, "bool encode fail") - expected := []byte{0x00} - if i == 1 { - expected = []byte{0x80} - } - require.Equal(t, expected, boolEncode, "encode bool not match with expected") - } - - // encoding test for byte values - for i := 0; i < byteTestCaseCount; i++ { - byteEncode, err := byteType.Encode(byte(i)) - require.NoError(t, err, "byte encode fail") - expected := []byte{byte(i)} - require.Equal(t, expected, byteEncode, "encode byte not match with expected") - } - - // encoding test for string values, since strings in ABI contain utf-8 symbols - // we use `gobberish` to generate random utf-8 symbols - // randomly generate utf-8 str from length 1 to 100, each length draw 10 random strs - // check if encoded ABI str match with expected value - for length := 1; length <= stringTestCaseCount; length++ { - for i := 0; i < stringTestCaseSpecLenCount; i++ { - // generate utf8 strings from `gobberish` at some length - utf8Str := gobberish.GenerateString(length) - // since string is just type alias of `byte[]`, we need to store number of bytes in encoding - utf8ByteLen := len([]byte(utf8Str)) - lengthBytes := make([]byte, 2) - binary.BigEndian.PutUint16(lengthBytes, uint16(utf8ByteLen)) - expected := append(lengthBytes, []byte(utf8Str)...) - - strEncode, err := stringType.Encode(utf8Str) - 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} - staticBoolArrType := makeStaticArrayType(boolType, 5) - t.Run("static bool array encoding", func(t *testing.T) { - inputBase := []bool{true, false, false, true, true} - expected := []byte{ - 0b10011000, - } - boolArrEncode, err := staticBoolArrType.Encode(inputBase) - require.NoError(t, err, "static bool array encoding should not return error") - require.Equal(t, expected, boolArrEncode, "static bool array encode not match expected") - }) - - // encoding test for static bool array - // input: {F, F, F, T, T, F, T, F, T, F, T}, encode expected: {0b00011010, 0b10100000} - staticBoolArrType = makeStaticArrayType(boolType, 11) - t.Run("static bool array encoding", func(t *testing.T) { - inputBase := []bool{false, false, false, true, true, false, true, false, true, false, true} - expected := []byte{ - 0b00011010, 0b10100000, - } - boolArrEncode, err := staticBoolArrType.Encode(inputBase) - require.NoError(t, err, "static bool array encoding should not return error") - require.Equal(t, expected, boolArrEncode, "static bool array encode not match expected") - }) - - // encoding test for dynamic bool array - // input: {F, T, F, T, F, T, F, T, F, T}, encode expected: {0b01010101, 0b01000000} - dynamicBoolArrayType := makeDynamicArrayType(boolType) - t.Run("dynamic bool array encoding", func(t *testing.T) { - inputBase := []bool{false, true, false, true, false, true, false, true, false, true} - expected := []byte{ - 0x00, 0x0A, 0b01010101, 0b01000000, - } - boolArrEncode, err := dynamicBoolArrayType.Encode(inputBase) - require.NoError(t, err, "dynamic bool array encoding should not return error") - require.Equal(t, expected, boolArrEncode, "dynamic bool array encode not match expected") - }) - - // encoding test for dynamic tuple values - // input type: (string, bool, bool, bool, bool, string) - // input value: ("ABC", T, F, T, F, "DEF") - /* - encode expected: - 0x00, 0x05 (first string start at 5th byte) - 0b10100000 (4 bool tuple element compacted together) - 0x00, 0x0A (second string start at 10th byte) - 0x00, 0x03 (first string byte length 3) - byte('A'), byte('B'), byte('C') (first string encoded bytes) - 0x00, 0x03 (second string byte length 3) - byte('D'), byte('E'), byte('F') (second string encoded bytes) - */ - tupleType, err := TypeOf("(string,bool,bool,bool,bool,string)") - require.NoError(t, err, "type from string for dynamic tuple type should not return error") - t.Run("dynamic tuple encoding", func(t *testing.T) { - inputBase := []interface{}{ - "ABC", true, false, true, false, "DEF", - } - expected := []byte{ - 0x00, 0x05, 0b10100000, 0x00, 0x0A, - 0x00, 0x03, byte('A'), byte('B'), byte('C'), - 0x00, 0x03, byte('D'), byte('E'), byte('F'), - } - stringTupleEncode, err := tupleType.Encode(inputBase) - require.NoError(t, err, "string tuple encoding should not return error") - require.Equal(t, expected, stringTupleEncode, "string tuple encoding not match expected") - }) - - // encoding test for tuples with static bool arrays - // input type: {bool[2], bool[2]} - // input value: ({T, T}, {T, T}) - /* - encode expected: - 0b11000000 (first static bool array) - 0b11000000 (second static bool array) - */ - tupleType, err = TypeOf("(bool[2],bool[2])") - require.NoError(t, err, "type from string for tuple type should not return error") - t.Run("static bool array tuple encoding", func(t *testing.T) { - expected := []byte{ - 0b11000000, - 0b11000000, - } - actual, err := tupleType.Encode([]interface{}{ - []bool{true, true}, - []bool{true, true}, - }) - require.NoError(t, err, "encode tuple value should not return error") - require.Equal(t, expected, actual, "encode static bool tuple should be equal") - }) - - // encoding test for tuples with static and dynamic bool arrays - // input type: (bool[2], bool[]) - // input value: ({T, T}, {T, T}) - /* - encode expected: - 0b11000000 (first static bool array) - 0x00, 0x03 (second dynamic bool array starts at 3rd byte) - 0x00, 0x02 (dynamic bool array length 2) - 0b11000000 (second static bool array) - */ - tupleType, err = TypeOf("(bool[2],bool[])") - require.NoError(t, err, "type from string for tuple type should not return error") - t.Run("static/dynamic bool array tuple encoding", func(t *testing.T) { - expected := []byte{ - 0b11000000, - 0x00, 0x03, - 0x00, 0x02, 0b11000000, - } - actual, err := tupleType.Encode([]interface{}{ - []bool{true, true}, - []bool{true, true}, - }) - require.NoError(t, err, "tuple value encoding should not return error") - require.Equal(t, expected, actual, "encode static/dynamic bool array tuple should not return error") - }) - - // encoding test for tuples with all dynamic bool arrays - // input type: (bool[], bool[]) - // input values: ({}, {}) - /* - encode expected: - 0x00, 0x04 (first dynamic bool array starts at 4th byte) - 0x00, 0x06 (second dynamic bool array starts at 6th byte) - 0x00, 0x00 (first dynamic bool array length 0) - 0x00, 0x00 (second dynamic bool array length 0) - */ - tupleType, err = TypeOf("(bool[],bool[])") - require.NoError(t, err, "type from string for tuple type should not return error") - t.Run("empty dynamic array tuple encoding", func(t *testing.T) { - expected := []byte{ - 0x00, 0x04, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, - } - actual, err := tupleType.Encode([]interface{}{ - []bool{}, []bool{}, - }) - require.NoError(t, err, "encode empty dynamic array tuple should not return error") - require.Equal(t, expected, actual, "encode empty dynamic array tuple does not match with expected") - }) - - // encoding test for empty tuple - // input: (), expected encoding: "" - tupleType, err = TypeOf("()") - require.NoError(t, err, "type from string for tuple type should not return error") - t.Run("empty tuple encoding", func(t *testing.T) { - expected := make([]byte, 0) - actual, err := tupleType.Encode([]interface{}{}) - require.NoError(t, err, "encode empty tuple should not return error") - require.Equal(t, expected, actual, "empty tuple encode should not return error") - }) -} - -func TestDecodeValid(t *testing.T) { - partitiontest.PartitionTest(t) - // decoding test for uint, iterating through all valid uint bitSize - // randomly take 1000 tests on each valid bitSize - // generate bytes from random uint values and decode bytes with additional type information - for intSize := uintBegin; intSize <= uintEnd; intSize += uintStepLength { - upperLimit := new(big.Int).Lsh(big.NewInt(1), uint(intSize)) - uintType, err := makeUintType(intSize) - require.NoError(t, err, "make uint type failure") - for i := 0; i < uintRandomTestPoints; i++ { - randBig, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - var expected interface{} - if intSize <= 64 && intSize > 32 { - expected = randBig.Uint64() - } else if intSize <= 32 && intSize > 16 { - expected = uint32(randBig.Uint64()) - } else if intSize == 16 { - expected = uint16(randBig.Uint64()) - } else if intSize == 8 { - expected = uint8(randBig.Uint64()) - } else { - expected = randBig - } - - encodedUint, err := uintType.Encode(expected) - require.NoError(t, err, "uint encode fail") - - actual, err := uintType.Decode(encodedUint) - require.NoError(t, err, "decoding uint should not return error") - require.Equal(t, expected, actual, "decode uint fail to match expected value") - } - } - - // decoding test for ufixed, iterating through all valid ufixed bitSize and precision - // randomly take 10 tests on each valid setting - // generate ufixed bytes and try to decode back with additional type information - for size := uintBegin; size <= uintEnd; size += uintStepLength { - upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) - for precision := 1; precision <= ufixedPrecision; precision++ { - ufixedType, err := makeUfixedType(size, precision) - require.NoError(t, err, "make ufixed type failure") - for i := 0; i < ufixedRandomTestPoints; i++ { - randBig, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - var expected interface{} - if size <= 64 && size > 32 { - expected = randBig.Uint64() - } else if size <= 32 && size > 16 { - expected = uint32(randBig.Uint64()) - } else if size == 16 { - expected = uint16(randBig.Uint64()) - } else if size == 8 { - expected = uint8(randBig.Uint64()) - } else { - expected = randBig - } - - encodedUfixed, err := ufixedType.Encode(expected) - require.NoError(t, err, "ufixed encode fail") - require.NoError(t, err, "cast big integer to expected value should not return error") - - actual, err := ufixedType.Decode(encodedUfixed) - require.NoError(t, err, "decoding ufixed should not return error") - require.Equal(t, expected, actual, "decode ufixed fail to match expected value") - } - } - } - - // decoding test for address, randomly take 300 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 := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3) - for i := 0; i < addressTestCaseCount; i++ { - randomAddrInt, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - expected := make([]byte, addressByteSize) - randomAddrInt.FillBytes(expected) - - actual, err := addressType.Decode(expected) - require.NoError(t, err, "decoding address should not return error") - require.Equal(t, expected, actual, "decode addr not match with expected") - } - - // bool value decoding test - for i := 0; i < 2; i++ { - boolEncode, err := boolType.Encode(i == 1) - require.NoError(t, err, "bool encode fail") - actual, err := boolType.Decode(boolEncode) - require.NoError(t, err, "decoding bool should not return error") - require.Equal(t, i == 1, actual, "decode bool not match with expected") - } - - // byte value decoding test, iterating through 256 valid byte value - for i := 0; i < byteTestCaseCount; i++ { - byteEncode, err := byteType.Encode(byte(i)) - require.NoError(t, err, "byte encode fail") - actual, err := byteType.Decode(byteEncode) - require.NoError(t, err, "decoding byte should not return error") - require.Equal(t, byte(i), actual, "decode byte not match with expected") - } - - // string value decoding test, test from utf string length 1 to 10 - // randomly take 5 utf-8 strings to make ABI string values - // decode the encoded expected value and check if they match - for length := 1; length <= stringTestCaseCount; length++ { - for i := 0; i < stringTestCaseSpecLenCount; i++ { - expected := gobberish.GenerateString(length) - strEncode, err := stringType.Encode(expected) - require.NoError(t, err, "string encode fail") - actual, err := stringType.Decode(strEncode) - require.NoError(t, err, "decoding string should not return error") - require.Equal(t, expected, actual, "encode string not match with expected") - } - } - - // decoding test for static bool array - // expected value: bool[5]: {T, F, F, T, T} - // input: 0b10011000 - t.Run("static bool array decode", func(t *testing.T) { - staticBoolArrT, err := TypeOf("bool[5]") - require.NoError(t, err, "make static bool array type failure") - expected := []interface{}{true, false, false, true, true} - actual, err := staticBoolArrT.Decode([]byte{0b10011000}) - 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) { - staticBoolArrT, err := TypeOf("bool[11]") - require.NoError(t, err, "make static bool array type failure") - expected := []interface{}{false, false, false, true, true, false, true, false, true, false, true} - actual, err := staticBoolArrT.Decode([]byte{0b00011010, 0b10100000}) - require.NoError(t, err, "decoding static bool array should not return error") - require.Equal(t, expected, actual, "static bool array decode do not match expected") - }) - - // decoding test for static uint array - // expected input: uint64[8]: {1, 2, 3, 4, 5, 6, 7, 8} - /* - input: 0, 0, 0, 0, 0, 0, 0, 1 (encoding for uint64 1) - 0, 0, 0, 0, 0, 0, 0, 2 (encoding for uint64 2) - 0, 0, 0, 0, 0, 0, 0, 3 (encoding for uint64 3) - 0, 0, 0, 0, 0, 0, 0, 4 (encoding for uint64 4) - 0, 0, 0, 0, 0, 0, 0, 5 (encoding for uint64 5) - 0, 0, 0, 0, 0, 0, 0, 6 (encoding for uint64 6) - 0, 0, 0, 0, 0, 0, 0, 7 (encoding for uint64 7) - 0, 0, 0, 0, 0, 0, 0, 8 (encoding for uint64 8) - */ - t.Run("static uint array decode", func(t *testing.T) { - staticUintArrT, err := TypeOf("uint64[8]") - require.NoError(t, err, "make static uint array type failure") - expected := []interface{}{ - uint64(1), uint64(2), - uint64(3), uint64(4), - uint64(5), uint64(6), - uint64(7), uint64(8), - } - arrayEncoded, err := staticUintArrT.Encode(expected) - require.NoError(t, err, "uint64 static array encode should not return error") - actual, err := staticUintArrT.Decode(arrayEncoded) - require.NoError(t, err, "uint64 static array decode should not return error") - require.Equal(t, expected, actual, "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) { - dynamicBoolArrT, err := TypeOf("bool[]") - require.NoError(t, err, "make dynamic bool array type failure") - expected := []interface{}{false, true, false, true, false, true, false, true, false, true} - inputEncoded := []byte{ - 0x00, 0x0A, 0b01010101, 0b01000000, - } - actual, err := dynamicBoolArrT.Decode(inputEncoded) - require.NoError(t, err, "decode dynamic array should not return error") - require.Equal(t, expected, actual, "decode dynamic array do not match expected") - }) - - // decoding test for dynamic tuple values - // expected value type: (string, bool, bool, bool, bool, string) - // expected value: ("ABC", T, F, T, F, "DEF") - /* - input bytes: - 0x00, 0x05 (first string start at 5th byte) - 0b10100000 (4 bool tuple element compacted together) - 0x00, 0x0A (second string start at 10th byte) - 0x00, 0x03 (first string byte length 3) - byte('A'), byte('B'), byte('C') (first string encoded bytes) - 0x00, 0x03 (second string byte length 3) - byte('D'), byte('E'), byte('F') (second string encoded bytes) - */ - t.Run("dynamic tuple decoding", func(t *testing.T) { - tupleT, err := TypeOf("(string,bool,bool,bool,bool,string)") - require.NoError(t, err, "make tuple type failure") - inputEncode := []byte{ - 0x00, 0x05, 0b10100000, 0x00, 0x0A, - 0x00, 0x03, byte('A'), byte('B'), byte('C'), - 0x00, 0x03, byte('D'), byte('E'), byte('F'), - } - expected := []interface{}{ - "ABC", true, false, true, false, "DEF", - } - actual, err := tupleT.Decode(inputEncode) - require.NoError(t, err, "decoding dynamic tuple should not return error") - require.Equal(t, expected, actual, "dynamic tuple not match with expected") - }) - - // decoding test for tuple with static bool array - // expected type: (bool[2], bool[2]) - // expected value: ({T, T}, {T, T}) - /* - input bytes: - 0b11000000 (first static bool array) - 0b11000000 (second static bool array) - */ - t.Run("static bool array tuple decoding", func(t *testing.T) { - tupleT, err := TypeOf("(bool[2],bool[2])") - require.NoError(t, err, "make tuple type failure") - expected := []interface{}{ - []interface{}{true, true}, - []interface{}{true, true}, - } - encodedInput := []byte{ - 0b11000000, - 0b11000000, - } - actual, err := tupleT.Decode(encodedInput) - require.NoError(t, err, "decode tuple value should not return error") - require.Equal(t, expected, actual, "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) { - tupleT, err := TypeOf("(bool[2],bool[])") - require.NoError(t, err, "make tuple type failure") - expected := []interface{}{ - []interface{}{true, true}, - []interface{}{true, true}, - } - encodedInput := []byte{ - 0b11000000, - 0x00, 0x03, - 0x00, 0x02, 0b11000000, - } - actual, err := tupleT.Decode(encodedInput) - require.NoError(t, err, "decode tuple for static/dynamic bool array should not return error") - require.Equal(t, expected, actual, "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) { - tupleT, err := TypeOf("(bool[],bool[])") - require.NoError(t, err, "make tuple type failure") - expected := []interface{}{ - []interface{}{}, []interface{}{}, - } - encodedInput := []byte{ - 0x00, 0x04, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, - } - actual, err := tupleT.Decode(encodedInput) - require.NoError(t, err, "decode tuple for empty dynamic array should not return error") - require.Equal(t, expected, actual, "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) { - tupleT, err := TypeOf("()") - require.NoError(t, err, "make empty tuple type should not return error") - actual, err := tupleT.Decode([]byte{}) - require.NoError(t, err, "decode empty tuple should not return error") - require.Equal(t, []interface{}{}, actual, "empty tuple encode should not return error") - }) -} - -func TestDecodeInvalid(t *testing.T) { - partitiontest.PartitionTest(t) - // decoding test for *corrupted* static bool array - // expected 9 elements for static bool array - // encoded bytes have only 8 bool values - // should throw error - t.Run("corrupted static bool array decode", func(t *testing.T) { - inputBase := []byte{0b11111111} - arrayType := makeStaticArrayType(boolType, 9) - _, err := arrayType.Decode(inputBase) - 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(boolType, 8) - _, err := arrayType.Decode(inputBase) - require.Error(t, err, "decoding corrupted static bool array should return error") - }) - - // decoding test for *corrupted* static uint array - // expected 8 uint elements in static uint64[8] array - // encoded bytes provide only 7 uint64 encoding - // should throw error - t.Run("static uint array decode", func(t *testing.T) { - inputBase := []byte{ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 2, - 0, 0, 0, 0, 0, 0, 0, 3, - 0, 0, 0, 0, 0, 0, 0, 4, - 0, 0, 0, 0, 0, 0, 0, 5, - 0, 0, 0, 0, 0, 0, 0, 6, - } - uintTArray, err := TypeOf("uint64[8]") - require.NoError(t, err, "make uint64 static array type should not return error") - _, err = uintTArray.Decode(inputBase) - require.Error(t, err, "corrupted uint64 static array decode should return error") - }) - - // decoding test for *corrupted* static uint array - // expected 7 uint elements in static uint64[7] array - // encoded bytes provide 8 uint64 encoding (one more uint64: 7) - // should throw error - t.Run("static uint array decode", func(t *testing.T) { - inputBase := []byte{ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 2, - 0, 0, 0, 0, 0, 0, 0, 3, - 0, 0, 0, 0, 0, 0, 0, 4, - 0, 0, 0, 0, 0, 0, 0, 5, - 0, 0, 0, 0, 0, 0, 0, 6, - 0, 0, 0, 0, 0, 0, 0, 7, - } - uintTArray, err := TypeOf("uint64[7]") - require.NoError(t, err, "make uint64 static array type should not return error") - _, err = uintTArray.Decode(inputBase) - require.Error(t, err, "corrupted uint64 static array decode should return error") - }) - - // decoding test for *corrupted* dynamic bool array - // expected 0x0A (10) bool elements in encoding head - // encoded bytes provide only 8 bool elements - // should throw error - t.Run("corrupted dynamic bool array decode", func(t *testing.T) { - inputBase := []byte{ - 0x00, 0x0A, 0b10101010, - } - dynamicT := makeDynamicArrayType(boolType) - _, err := dynamicT.Decode(inputBase) - require.Error(t, err, "decode corrupted dynamic array should return error") - }) - - // decoding test for *corrupted* dynamic bool array - // expected 0x07 (7) bool elements in encoding head - // encoded bytes provide 1 byte more (0b00000000) - // should throw error - t.Run("corrupted dynamic bool array decode", func(t *testing.T) { - inputBase := []byte{ - 0x00, 0x07, 0b10101010, 0b00000000, - } - dynamicT := makeDynamicArrayType(boolType) - _, err := dynamicT.Decode(inputBase) - require.Error(t, err, "decode corrupted dynamic array should return error") - }) - - // decoding test for *corrupted* dynamic tuple value - // expected type: (string, bool, bool, bool, bool, string) - // expected value: ("ABC", T, F, T, F, "DEF") - /* - corrupted bytes: - 0x00, 0x04 (corrupted: first string start at 4th byte, should be 5th) - 0b10100000 (4 bool tuple element compacted together) - 0x00, 0x0A (second string start at 10th byte) - 0x00, 0x03 (first string byte length 3) - byte('A'), byte('B'), byte('C') (first string encoded bytes) - 0x00, 0x03 (second string byte length 3) - byte('D'), byte('E'), byte('F') (second string encoded bytes) - */ - // the result would be: first string have length 0x0A, 0x00 - // the length exceeds the segment it allocated: 0x0A, 0x00, 0x03, byte('A'), byte('B'), byte('C') - // should throw error - t.Run("corrupted dynamic tuple decoding", func(t *testing.T) { - inputEncode := []byte{ - 0x00, 0x04, 0b10100000, 0x00, 0x0A, - 0x00, 0x03, byte('A'), byte('B'), byte('C'), - 0x00, 0x03, byte('D'), byte('E'), byte('F'), - } - tupleT, err := TypeOf("(string,bool,bool,bool,bool,string)") - require.NoError(t, err, "make tuple type failure") - _, err = tupleT.Decode(inputEncode) - 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, err := TypeOf("(bool[2],bool[2])") - require.NoError(t, err, "make tuple type failure") - encodedInput0 := []byte{ - 0b11000000, - 0b11000000, - 0b00000000, - } - _, err = expectedType.Decode(encodedInput0) - require.Error(t, err, "decode corrupted tuple value should return error") - - encodedInput1 := []byte{ - 0b11000000, - } - _, err = expectedType.Decode(encodedInput1) - 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, - } - tupleT, err := TypeOf("(bool[2],bool[])") - require.NoError(t, err, "make tuple type failure") - _, err = tupleT.Decode(encodedInput) - 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, - } - tupleT, err := TypeOf("(bool[],bool[])") - require.NoError(t, err, "make tuple type failure") - _, err = tupleT.Decode(encodedInput) - 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} - tupleT, err := TypeOf("()") - require.NoError(t, err, "make tuple type failure") - _, err = tupleT.Decode(encodedInput) - require.Error(t, err, "decode corrupted empty tuple should return error") - }) -} - -type testUnit struct { - serializedType string - value interface{} -} - -func categorySelfRoundTripTest(t *testing.T, category []testUnit) { - for _, testObj := range category { - abiType, err := TypeOf(testObj.serializedType) - require.NoError(t, err, "failure to deserialize type: "+testObj.serializedType) - encodedValue, err := abiType.Encode(testObj.value) - require.NoError(t, err, - "failure to encode value %#v over type %s", testObj.value, testObj.serializedType, - ) - actual, err := abiType.Decode(encodedValue) - require.NoError(t, err, - "failure to decode value %#v for type %s", encodedValue, testObj.serializedType, - ) - require.Equal(t, testObj.value, actual, - "decoded value %#v not equal to expected value %#v", actual, testObj.value, - ) - jsonEncodedValue, err := abiType.MarshalToJSON(testObj.value) - require.NoError(t, err, - "failure to encode value %#v to JSON type", testObj.value, - ) - jsonActual, err := abiType.UnmarshalFromJSON(jsonEncodedValue) - require.NoError(t, err, - "failure to decode JSON value %s back for type %s", - string(jsonEncodedValue), testObj.serializedType, - ) - require.Equal(t, testObj.value, jsonActual, - "decode JSON value %s not equal to expected %s", jsonActual, testObj.value, - ) - } -} - -func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) { - (*pool)[Uint] = make([]testUnit, uintTestCaseCount*uintEnd/uintStepLength) - (*pool)[Ufixed] = make([]testUnit, ufixedPrecision*uintEnd/uintStepLength) - - uintIndex := 0 - ufixedIndex := 0 - - for bitSize := uintBegin; bitSize <= uintEnd; bitSize += uintStepLength { - max := new(big.Int).Lsh(big.NewInt(1), uint(bitSize)) - - uintT, err := makeUintType(bitSize) - require.NoError(t, err, "make uint type failure") - uintTstr := uintT.String() - - for j := 0; j < uintTestCaseCount; j++ { - randVal, err := rand.Int(rand.Reader, max) - require.NoError(t, err, "generate random uint, should be no error") - - narrowest, err := castBigIntToNearestPrimitive(randVal, uint16(bitSize)) - require.NoError(t, err, "cast random uint to nearest primitive failure") - - (*pool)[Uint][uintIndex] = testUnit{serializedType: uintTstr, value: narrowest} - uintIndex++ - } - - for precision := 1; precision <= ufixedPrecision; precision++ { - randVal, err := rand.Int(rand.Reader, max) - require.NoError(t, err, "generate random ufixed, should be no error") - - narrowest, err := castBigIntToNearestPrimitive(randVal, uint16(bitSize)) - require.NoError(t, err, "cast random uint to nearest primitive failure") - - ufixedT, err := makeUfixedType(bitSize, precision) - require.NoError(t, err, "make ufixed type failure") - ufixedTstr := ufixedT.String() - (*pool)[Ufixed][ufixedIndex] = testUnit{serializedType: ufixedTstr, value: narrowest} - ufixedIndex++ - } - } - categorySelfRoundTripTest(t, (*pool)[Uint]) - categorySelfRoundTripTest(t, (*pool)[Ufixed]) - - (*pool)[Byte] = make([]testUnit, byteTestCaseCount) - for i := 0; i < byteTestCaseCount; i++ { - (*pool)[Byte][i] = testUnit{serializedType: byteType.String(), value: byte(i)} - } - categorySelfRoundTripTest(t, (*pool)[Byte]) - - (*pool)[Bool] = make([]testUnit, boolTestCaseCount) - (*pool)[Bool][0] = testUnit{serializedType: boolType.String(), value: false} - (*pool)[Bool][1] = testUnit{serializedType: boolType.String(), value: true} - categorySelfRoundTripTest(t, (*pool)[Bool]) - - maxAddress := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3) - (*pool)[Address] = make([]testUnit, addressTestCaseCount) - for i := 0; i < addressTestCaseCount; i++ { - randAddrVal, err := rand.Int(rand.Reader, maxAddress) - require.NoError(t, err, "generate random value for address, should be no error") - addrBytes := make([]byte, addressByteSize) - randAddrVal.FillBytes(addrBytes) - (*pool)[Address][i] = testUnit{serializedType: addressType.String(), value: addrBytes} - } - categorySelfRoundTripTest(t, (*pool)[Address]) - - (*pool)[String] = make([]testUnit, stringTestCaseCount*stringTestCaseSpecLenCount) - stringIndex := 0 - for length := 1; length <= stringTestCaseCount; length++ { - for i := 0; i < stringTestCaseSpecLenCount; i++ { - (*pool)[String][stringIndex] = testUnit{ - serializedType: stringType.String(), - value: gobberish.GenerateString(length), - } - stringIndex++ - } - } - categorySelfRoundTripTest(t, (*pool)[String]) -} - -func takeSomeFromCategoryAndGenerateArray( - t *testing.T, abiT BaseType, srtIndex int, takeNum uint16, pool *map[BaseType][]testUnit) { - - tempArray := make([]interface{}, takeNum) - for i := 0; i < int(takeNum); i++ { - index := srtIndex + i - if index >= len((*pool)[abiT]) { - index = srtIndex - } - tempArray[i] = (*pool)[abiT][index].value - } - tempT, err := TypeOf((*pool)[abiT][srtIndex].serializedType) - require.NoError(t, err, "type in test uint cannot be deserialized") - (*pool)[ArrayStatic] = append((*pool)[ArrayStatic], testUnit{ - serializedType: makeStaticArrayType(tempT, takeNum).String(), - value: tempArray, - }) - (*pool)[ArrayDynamic] = append((*pool)[ArrayDynamic], testUnit{ - serializedType: makeDynamicArrayType(tempT).String(), - value: tempArray, - }) -} - -func addArrayRandomValues(t *testing.T, pool *map[BaseType][]testUnit) { - for intIndex := 0; intIndex < len((*pool)[Uint]); intIndex += uintTestCaseCount { - takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, takeNum, pool) - } - takeSomeFromCategoryAndGenerateArray(t, Byte, 0, takeNum, pool) - takeSomeFromCategoryAndGenerateArray(t, Address, 0, takeNum, pool) - takeSomeFromCategoryAndGenerateArray(t, String, 0, takeNum, pool) - takeSomeFromCategoryAndGenerateArray(t, Bool, 0, takeNum, pool) - - categorySelfRoundTripTest(t, (*pool)[ArrayStatic]) - categorySelfRoundTripTest(t, (*pool)[ArrayDynamic]) -} - -func addTupleRandomValues(t *testing.T, slotRange BaseType, pool *map[BaseType][]testUnit) { - for i := 0; i < tupleTestCaseCount; i++ { - tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(tupleMaxLength)) - require.NoError(t, err, "generate random tuple length should not return error") - tupleLen := tupleLenBig.Int64() + 1 - testUnits := make([]testUnit, tupleLen) - for index := 0; index < int(tupleLen); index++ { - tupleTypeIndexBig, err := rand.Int(rand.Reader, big.NewInt(int64(slotRange)+1)) - require.NoError(t, err, "generate random tuple element type index should not return error") - tupleTypeIndex := BaseType(tupleTypeIndexBig.Int64()) - tupleElemChoiceRange := len((*pool)[tupleTypeIndex]) - - tupleElemRangeIndexBig, err := rand.Int(rand.Reader, big.NewInt(int64(tupleElemChoiceRange))) - require.NoError(t, err, "generate random tuple element index in test pool should not return error") - tupleElemRangeIndex := tupleElemRangeIndexBig.Int64() - tupleElem := (*pool)[tupleTypeIndex][tupleElemRangeIndex] - testUnits[index] = tupleElem - } - elemValues := make([]interface{}, tupleLen) - elemTypes := make([]Type, tupleLen) - for index := 0; index < int(tupleLen); index++ { - elemValues[index] = testUnits[index].value - abiT, err := TypeOf(testUnits[index].serializedType) - require.NoError(t, err, "deserialize type failure for tuple elements") - elemTypes[index] = abiT - } - tupleT, err := MakeTupleType(elemTypes) - require.NoError(t, err, "make tuple type failure") - (*pool)[Tuple] = append((*pool)[Tuple], testUnit{ - serializedType: tupleT.String(), - value: elemValues, - }) - } -} - -func TestRandomABIEncodeDecodeRoundTrip(t *testing.T) { - partitiontest.PartitionTest(t) - testValuePool := make(map[BaseType][]testUnit) - addPrimitiveRandomValues(t, &testValuePool) - addArrayRandomValues(t, &testValuePool) - addTupleRandomValues(t, String, &testValuePool) - addTupleRandomValues(t, Tuple, &testValuePool) - categorySelfRoundTripTest(t, testValuePool[Tuple]) -} - -func TestParseArgJSONtoByteSlice(t *testing.T) { - partitiontest.PartitionTest(t) - - makeRepeatSlice := func(size int, value string) []string { - slice := make([]string, size) - for i := range slice { - slice[i] = value - } - return slice - } - - tests := []struct { - argTypes []string - jsonArgs []string - expectedAppArgs [][]byte - }{ - { - argTypes: []string{}, - jsonArgs: []string{}, - expectedAppArgs: [][]byte{}, - }, - { - argTypes: []string{"uint8"}, - jsonArgs: []string{"100"}, - expectedAppArgs: [][]byte{{100}}, - }, - { - argTypes: []string{"uint8", "uint16"}, - jsonArgs: []string{"100", "65535"}, - expectedAppArgs: [][]byte{{100}, {255, 255}}, - }, - { - argTypes: makeRepeatSlice(15, "string"), - jsonArgs: []string{ - `"a"`, - `"b"`, - `"c"`, - `"d"`, - `"e"`, - `"f"`, - `"g"`, - `"h"`, - `"i"`, - `"j"`, - `"k"`, - `"l"`, - `"m"`, - `"n"`, - `"o"`, - }, - expectedAppArgs: [][]byte{ - {00, 01, 97}, - {00, 01, 98}, - {00, 01, 99}, - {00, 01, 100}, - {00, 01, 101}, - {00, 01, 102}, - {00, 01, 103}, - {00, 01, 104}, - {00, 01, 105}, - {00, 01, 106}, - {00, 01, 107}, - {00, 01, 108}, - {00, 01, 109}, - {00, 01, 110}, - {00, 01, 111}, - }, - }, - { - argTypes: makeRepeatSlice(16, "string"), - jsonArgs: []string{ - `"a"`, - `"b"`, - `"c"`, - `"d"`, - `"e"`, - `"f"`, - `"g"`, - `"h"`, - `"i"`, - `"j"`, - `"k"`, - `"l"`, - `"m"`, - `"n"`, - `"o"`, - `"p"`, - }, - expectedAppArgs: [][]byte{ - {00, 01, 97}, - {00, 01, 98}, - {00, 01, 99}, - {00, 01, 100}, - {00, 01, 101}, - {00, 01, 102}, - {00, 01, 103}, - {00, 01, 104}, - {00, 01, 105}, - {00, 01, 106}, - {00, 01, 107}, - {00, 01, 108}, - {00, 01, 109}, - {00, 01, 110}, - {00, 04, 00, 07, 00, 01, 111, 00, 01, 112}, - }, - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("index=%d", i), func(t *testing.T) { - applicationArgs := [][]byte{} - err := ParseArgJSONtoByteSlice(test.argTypes, test.jsonArgs, &applicationArgs) - require.NoError(t, err) - require.Equal(t, test.expectedAppArgs, applicationArgs) - }) - } -} - -func TestParseMethodSignature(t *testing.T) { - partitiontest.PartitionTest(t) - - tests := []struct { - signature string - name string - argTypes []string - returnType string - }{ - { - signature: "add(uint8,uint16,pay,account,txn)uint32", - name: "add", - argTypes: []string{"uint8", "uint16", "pay", "account", "txn"}, - returnType: "uint32", - }, - { - signature: "nothing()void", - name: "nothing", - argTypes: []string{}, - returnType: "void", - }, - { - signature: "tupleArgs((uint8,uint128),account,(string,(bool,bool)))bool", - name: "tupleArgs", - argTypes: []string{"(uint8,uint128)", "account", "(string,(bool,bool))"}, - returnType: "bool", - }, - { - signature: "tupleReturn(uint64)(bool,bool,bool)", - name: "tupleReturn", - argTypes: []string{"uint64"}, - returnType: "(bool,bool,bool)", - }, - { - signature: "tupleArgsAndReturn((uint8,uint128),account,(string,(bool,bool)))(bool,bool,bool)", - name: "tupleArgsAndReturn", - argTypes: []string{"(uint8,uint128)", "account", "(string,(bool,bool))"}, - returnType: "(bool,bool,bool)", - }, - } - - for _, test := range tests { - t.Run(test.signature, func(t *testing.T) { - name, argTypes, returnType, err := ParseMethodSignature(test.signature) - require.NoError(t, err) - require.Equal(t, test.name, name) - require.Equal(t, test.argTypes, argTypes) - require.Equal(t, test.returnType, returnType) - }) - } -} - -func TestInferToSlice(t *testing.T) { - partitiontest.PartitionTest(t) - - var emptySlice []int - tests := []struct { - toBeInferred interface{} - length int - }{ - { - toBeInferred: []int{}, - length: 0, - }, - { - toBeInferred: make([]int, 0), - length: 0, - }, - { - toBeInferred: emptySlice, - length: 0, - }, - { - toBeInferred: [0]int{}, - length: 0, - }, - { - toBeInferred: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, - length: 32, - }, - { - toBeInferred: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, - length: 32, - }, - } - - for i, test := range tests { - inferredSlice, err := inferToSlice(test.toBeInferred) - require.NoError(t, err, "inferToSlice on testcase %d failed to successfully infer %v", i, test.toBeInferred) - require.Equal(t, test.length, len(inferredSlice), "inferToSlice on testcase %d inferred different length, expected %d", i, test.length) - } - - // one more testcase for totally nil (with no type information) is bad, should not pass the test - _, err := inferToSlice(nil) - require.EqualError( - t, err, - "cannot infer an interface value as a slice of interface element", - "inferToSlice should return type inference error when passed in nil with unexpected Kind") - - // one moar testcase for wrong typed nil is bad, should not pass the test - var nilPt *uint64 = nil - _, err = inferToSlice(nilPt) - require.EqualError( - t, err, - "cannot infer an interface value as a slice of interface element", - "inferToSlice should return type inference error when passing argument type other than slice or array") -} diff --git a/data/abi/abi_json.go b/data/abi/abi_json.go deleted file mode 100644 index a71823f0ce..0000000000 --- a/data/abi/abi_json.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (C) 2019-2022 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 ( - "bytes" - "crypto/sha512" - "encoding/base32" - "encoding/json" - "fmt" - "math/big" -) - -// NOTE: discussion about go-algorand-sdk -// https://github.com/algorand/go-algorand/pull/3375#issuecomment-1007536841 - -var base32Encoder = base32.StdEncoding.WithPadding(base32.NoPadding) - -func addressCheckSum(addressBytes []byte) ([]byte, error) { - if len(addressBytes) != addressByteSize { - return nil, fmt.Errorf("address bytes should be of length 32") - } - hashed := sha512.Sum512_256(addressBytes[:]) - return hashed[addressByteSize-checksumByteSize:], nil -} - -func castBigIntToNearestPrimitive(num *big.Int, bitSize uint16) (interface{}, error) { - if num.BitLen() > int(bitSize) { - return nil, fmt.Errorf("cast big int to nearest primitive failure: %v >= 2^%d", num, bitSize) - } else if num.Sign() < 0 { - return nil, fmt.Errorf("cannot cast big int to near primitive: %v < 0", num) - } - - switch bitSize / 8 { - case 1: - return uint8(num.Uint64()), nil - case 2: - return uint16(num.Uint64()), nil - case 3, 4: - return uint32(num.Uint64()), nil - case 5, 6, 7, 8: - return num.Uint64(), nil - default: - return num, nil - } -} - -// MarshalToJSON convert golang value to JSON format from ABI type -func (t Type) MarshalToJSON(value interface{}) ([]byte, error) { - switch t.abiTypeID { - case Uint: - bytesUint, err := encodeInt(value, t.bitSize) - if err != nil { - return nil, err - } - return new(big.Int).SetBytes(bytesUint).MarshalJSON() - case Ufixed: - denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.precision)), nil) - encodedUint, err := encodeInt(value, t.bitSize) - if err != nil { - return nil, err - } - return []byte(new(big.Rat).SetFrac(new(big.Int).SetBytes(encodedUint), denom).FloatString(int(t.precision))), nil - case Bool: - boolValue, ok := value.(bool) - if !ok { - return nil, fmt.Errorf("cannot infer to bool for marshal to JSON") - } - return json.Marshal(boolValue) - case Byte: - byteValue, ok := value.(byte) - if !ok { - return nil, fmt.Errorf("cannot infer to byte for marshal to JSON") - } - return json.Marshal(byteValue) - case Address: - var addressValueInternal []byte - switch valueCasted := value.(type) { - case []byte: - if len(valueCasted) != addressByteSize { - return nil, fmt.Errorf("address byte slice length not equal to 32 byte") - } - addressValueInternal = valueCasted - case [addressByteSize]byte: - copy(addressValueInternal[:], valueCasted[:]) - default: - return nil, fmt.Errorf("cannot infer to byte slice/array for marshal to JSON") - } - checksum, err := addressCheckSum(addressValueInternal) - if err != nil { - return nil, err - } - addressValueInternal = append(addressValueInternal, checksum...) - return json.Marshal(base32Encoder.EncodeToString(addressValueInternal)) - case ArrayStatic, ArrayDynamic: - values, err := inferToSlice(value) - if err != nil { - return nil, err - } - if t.abiTypeID == ArrayStatic && int(t.staticLength) != len(values) { - return nil, fmt.Errorf("length of slice %d != type specific length %d", len(values), t.staticLength) - } - if t.childTypes[0].abiTypeID == Byte { - byteArr := make([]byte, len(values)) - for i := 0; i < len(values); i++ { - tempByte, ok := values[i].(byte) - if !ok { - return nil, fmt.Errorf("cannot infer byte element from slice") - } - byteArr[i] = tempByte - } - return json.Marshal(byteArr) - } - rawMsgSlice := make([]json.RawMessage, len(values)) - for i := 0; i < len(values); i++ { - rawMsgSlice[i], err = t.childTypes[0].MarshalToJSON(values[i]) - if err != nil { - return nil, err - } - } - return json.Marshal(rawMsgSlice) - case String: - stringVal, ok := value.(string) - if !ok { - return nil, fmt.Errorf("cannot infer to string for marshal to JSON") - } - return json.Marshal(stringVal) - case Tuple: - values, err := inferToSlice(value) - if err != nil { - return nil, err - } - if len(values) != int(t.staticLength) { - return nil, fmt.Errorf("tuple element number != value slice length") - } - rawMsgSlice := make([]json.RawMessage, len(values)) - for i := 0; i < len(values); i++ { - rawMsgSlice[i], err = t.childTypes[i].MarshalToJSON(values[i]) - if err != nil { - return nil, err - } - } - return json.Marshal(rawMsgSlice) - default: - return nil, fmt.Errorf("cannot infer ABI type for marshalling value to JSON") - } -} - -// UnmarshalFromJSON convert bytes to golang value following ABI type and encoding rules -func (t Type) UnmarshalFromJSON(jsonEncoded []byte) (interface{}, error) { - switch t.abiTypeID { - case Uint: - num := new(big.Int) - if err := num.UnmarshalJSON(jsonEncoded); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to uint: %v", string(jsonEncoded), err) - } - return castBigIntToNearestPrimitive(num, t.bitSize) - case Ufixed: - floatTemp := new(big.Rat) - if err := floatTemp.UnmarshalText(jsonEncoded); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to ufixed: %v", string(jsonEncoded), err) - } - denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.precision)), nil) - denomRat := new(big.Rat).SetInt(denom) - numeratorRat := new(big.Rat).Mul(denomRat, floatTemp) - if !numeratorRat.IsInt() { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to ufixed: precision out of range", string(jsonEncoded)) - } - return castBigIntToNearestPrimitive(numeratorRat.Num(), t.bitSize) - case Bool: - var elem bool - if err := json.Unmarshal(jsonEncoded, &elem); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to bool: %v", string(jsonEncoded), err) - } - return elem, nil - case Byte: - var elem byte - if err := json.Unmarshal(jsonEncoded, &elem); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded to byte: %v", err) - } - return elem, nil - case Address: - var addrStr string - if err := json.Unmarshal(jsonEncoded, &addrStr); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded to address string: %v", err) - } - decoded, err := base32Encoder.DecodeString(addrStr) - if err != nil { - return nil, - fmt.Errorf("cannot cast JSON encoded address string (%s) to address: %v", addrStr, err) - } - if len(decoded) != addressByteSize+checksumByteSize { - return nil, - fmt.Errorf( - "cannot cast JSON encoded address string (%s) to address: "+ - "decoded byte length should equal to 36 with address and checksum", - string(jsonEncoded), - ) - } - checksum, err := addressCheckSum(decoded[:addressByteSize]) - if err != nil { - return nil, err - } - if !bytes.Equal(checksum, decoded[addressByteSize:]) { - return nil, fmt.Errorf("cannot cast JSON encoded address string (%s) to address: decoded checksum unmatch", addrStr) - } - return decoded[:addressByteSize], nil - case ArrayStatic, ArrayDynamic: - if t.childTypes[0].abiTypeID == Byte && bytes.HasPrefix(jsonEncoded, []byte{'"'}) { - var byteArr []byte - err := json.Unmarshal(jsonEncoded, &byteArr) - if err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to bytes: %v", string(jsonEncoded), err) - } - if t.abiTypeID == ArrayStatic && len(byteArr) != int(t.staticLength) { - return nil, fmt.Errorf("length of slice %d != type specific length %d", len(byteArr), t.staticLength) - } - outInterface := make([]interface{}, len(byteArr)) - for i := 0; i < len(byteArr); i++ { - outInterface[i] = byteArr[i] - } - return outInterface, nil - } - var elems []json.RawMessage - if err := json.Unmarshal(jsonEncoded, &elems); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to array: %v", string(jsonEncoded), err) - } - if t.abiTypeID == ArrayStatic && len(elems) != int(t.staticLength) { - return nil, fmt.Errorf("JSON array element number != ABI array elem number") - } - values := make([]interface{}, len(elems)) - for i := 0; i < len(elems); i++ { - tempValue, err := t.childTypes[0].UnmarshalFromJSON(elems[i]) - if err != nil { - return nil, err - } - values[i] = tempValue - } - return values, nil - case String: - stringEncoded := string(jsonEncoded) - if bytes.HasPrefix(jsonEncoded, []byte{'"'}) { - var stringVar string - if err := json.Unmarshal(jsonEncoded, &stringVar); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string: %v", stringEncoded, err) - } - return stringVar, nil - } else if bytes.HasPrefix(jsonEncoded, []byte{'['}) { - var elems []byte - if err := json.Unmarshal(jsonEncoded, &elems); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string: %v", stringEncoded, err) - } - return string(elems), nil - } else { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string", stringEncoded) - } - case Tuple: - var elems []json.RawMessage - if err := json.Unmarshal(jsonEncoded, &elems); err != nil { - return nil, fmt.Errorf("cannot cast JSON encoded (%s) to array for tuple: %v", string(jsonEncoded), err) - } - if len(elems) != int(t.staticLength) { - return nil, fmt.Errorf("JSON array element number != ABI tuple elem number") - } - values := make([]interface{}, len(elems)) - for i := 0; i < len(elems); i++ { - tempValue, err := t.childTypes[i].UnmarshalFromJSON(elems[i]) - if err != nil { - return nil, err - } - values[i] = tempValue - } - return values, nil - default: - return nil, fmt.Errorf("cannot cast JSON encoded %s to ABI encoding stuff", string(jsonEncoded)) - } -} diff --git a/data/abi/abi_json_test.go b/data/abi/abi_json_test.go deleted file mode 100644 index 49083fdeaa..0000000000 --- a/data/abi/abi_json_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package abi - -import ( - "crypto/rand" - "math/big" - "testing" - - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" -) - -func TestRandomAddressEquality(t *testing.T) { - partitiontest.PartitionTest(t) - - upperLimit := new(big.Int).Lsh(big.NewInt(1), addressByteSize<<3) - var addrBasics basics.Address - var addrABI []byte = make([]byte, addressByteSize) - - for testCaseIndex := 0; testCaseIndex < addressTestCaseCount; testCaseIndex++ { - randomAddrInt, err := rand.Int(rand.Reader, upperLimit) - require.NoError(t, err, "cryptographic random int init fail") - - randomAddrInt.FillBytes(addrBasics[:]) - randomAddrInt.FillBytes(addrABI) - - checkSumBasics := addrBasics.GetChecksum() - checkSumABI, err := addressCheckSum(addrABI) - require.NoError(t, err, "ABI compute checksum for address slice failed") - - require.Equal(t, checkSumBasics, checkSumABI, - "basics.Address computed checksum %v not equal to data.abi computed checksum %v", - ) - } -} - -func TestJSONtoInterfaceValid(t *testing.T) { - partitiontest.PartitionTest(t) - var testCases = []struct { - input string - typeStr string - expected interface{} - }{ - { - input: `[true, [0, 1, 2], 17]`, - typeStr: `(bool,byte[],uint64)`, - expected: []interface{}{ - true, - []interface{}{byte(0), byte(1), byte(2)}, - uint64(17), - }, - }, - { - input: `[true, "AAEC", 17]`, - typeStr: `(bool,byte[],uint64)`, - expected: []interface{}{ - true, - []interface{}{byte(0), byte(1), byte(2)}, - uint64(17), - }, - }, - { - input: `"AQEEBQEE"`, - typeStr: `byte[6]`, - expected: []interface{}{byte(1), byte(1), byte(4), byte(5), byte(1), byte(4)}, - }, - { - input: `[[0, [true, false], "utf-8"], [18446744073709551615, [false, true], "pistachio"]]`, - typeStr: `(uint64,bool[2],string)[]`, - expected: []interface{}{ - []interface{}{uint64(0), []interface{}{true, false}, "utf-8"}, - []interface{}{^uint64(0), []interface{}{false, true}, "pistachio"}, - }, - }, - { - input: `[]`, - typeStr: `(uint64,bool[2],string)[]`, - expected: []interface{}{}, - }, - { - input: "[]", - typeStr: "()", - expected: []interface{}{}, - }, - { - input: "[65, 66, 67]", - typeStr: "string", - expected: "ABC", - }, - { - input: "[]", - typeStr: "string", - expected: "", - }, - { - input: "123.456", - typeStr: "ufixed64x3", - expected: uint64(123456), - }, - { - input: `"optin"`, - typeStr: "string", - expected: "optin", - }, - { - input: `"AAEC"`, - typeStr: "byte[3]", - expected: []interface{}{byte(0), byte(1), byte(2)}, - }, - { - input: `["uwu",["AAEC",12.34]]`, - typeStr: "(string,(byte[3],ufixed64x3))", - expected: []interface{}{"uwu", []interface{}{[]interface{}{byte(0), byte(1), byte(2)}, uint64(12340)}}, - }, - { - input: `[399,"should pass",[true,false,false,true]]`, - typeStr: "(uint64,string,bool[])", - expected: []interface{}{uint64(399), "should pass", []interface{}{true, false, false, true}}, - }, - } - - for _, testCase := range testCases { - abiT, err := TypeOf(testCase.typeStr) - require.NoError(t, err, "fail to construct ABI type (%s): %v", testCase.typeStr, err) - res, err := abiT.UnmarshalFromJSON([]byte(testCase.input)) - require.NoError(t, err, "fail to unmarshal JSON to interface: (%s): %v", testCase.input, err) - require.Equal(t, testCase.expected, res, "%v not matching with expected value %v", res, testCase.expected) - resEncoded, err := abiT.Encode(res) - require.NoError(t, err, "fail to encode %v to ABI bytes: %v", res, err) - resDecoded, err := abiT.Decode(resEncoded) - require.NoError(t, err, "fail to decode ABI bytes of %v: %v", res, err) - require.Equal(t, res, resDecoded, "ABI encode-decode round trip: %v not match with expected %v", resDecoded, res) - } -} diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go deleted file mode 100644 index aa4e0b75af..0000000000 --- a/data/abi/abi_type.go +++ /dev/null @@ -1,498 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package abi - -import ( - "fmt" - "math" - "regexp" - "strconv" - "strings" -) - -/* - ABI-Types: uint: An N-bit unsigned integer (8 <= N <= 512 and N % 8 = 0). - | byte (alias for uint8) - | ufixed x (8 <= N <= 512, N % 8 = 0, and 0 < M <= 160) - | bool - | address (alias for byte[32]) - | [] - | [] - | string - | (T1, ..., Tn) -*/ - -// BaseType is an type-alias for uint32. A BaseType value indicates the type of an ABI value. -type BaseType uint32 - -const ( - // Uint is the index (0) for `Uint` type in ABI encoding. - Uint BaseType = iota - // Byte is the index (1) for `Byte` type in ABI encoding. - Byte - // Ufixed is the index (2) for `UFixed` type in ABI encoding. - Ufixed - // Bool is the index (3) for `Bool` type in ABI encoding. - Bool - // ArrayStatic is the index (4) for static length array ([length]) type in ABI encoding. - ArrayStatic - // Address is the index (5) for `Address` type in ABI encoding (an type alias of Byte[32]). - Address - // ArrayDynamic is the index (6) for dynamic length array ([]) type in ABI encoding. - ArrayDynamic - // String is the index (7) for `String` type in ABI encoding (an type alias of Byte[]). - String - // Tuple is the index (8) for tuple `(, ..., )` in ABI encoding. - Tuple -) - -const ( - addressByteSize = 32 - checksumByteSize = 4 - singleByteSize = 1 - singleBoolSize = 1 - lengthEncodeByteSize = 2 - abiEncodingLengthLimit = 1 << 16 -) - -// Type is the struct that stores information about an ABI value's type. -type Type struct { - abiTypeID BaseType - childTypes []Type - - // only can be applied to `uint` bitSize or `ufixed` bitSize - bitSize uint16 - // only can be applied to `ufixed` precision - precision uint16 - - // length for static array / tuple - /* - by ABI spec, len over binary array returns number of bytes - the type is uint16, which allows for only length in [0, 2^16 - 1] - representation of static length can only be constrained in uint16 type - */ - // NOTE may want to change back to uint32/uint64 - staticLength uint16 -} - -// String serialize an ABI Type to a string in ABI encoding. -func (t Type) String() string { - switch t.abiTypeID { - case Uint: - return fmt.Sprintf("uint%d", t.bitSize) - case Byte: - return "byte" - case Ufixed: - return fmt.Sprintf("ufixed%dx%d", t.bitSize, t.precision) - case Bool: - return "bool" - case ArrayStatic: - return fmt.Sprintf("%s[%d]", t.childTypes[0].String(), t.staticLength) - case Address: - return "address" - case ArrayDynamic: - return t.childTypes[0].String() + "[]" - case String: - return "string" - case Tuple: - typeStrings := make([]string, len(t.childTypes)) - for i := 0; i < len(t.childTypes); i++ { - typeStrings[i] = t.childTypes[i].String() - } - return "(" + strings.Join(typeStrings, ",") + ")" - default: - panic("Type Serialization Error, fail to infer from abiTypeID (bruh you shouldn't be here)") - } -} - -var staticArrayRegexp = regexp.MustCompile(`^([a-z\d\[\](),]+)\[([1-9][\d]*)]$`) -var ufixedRegexp = regexp.MustCompile(`^ufixed([1-9][\d]*)x([1-9][\d]*)$`) - -// TypeOf parses an ABI type string. -// For example: `TypeOf("(uint64,byte[])")` -func TypeOf(str string) (Type, error) { - switch { - case strings.HasSuffix(str, "[]"): - arrayArgType, err := TypeOf(str[:len(str)-2]) - if err != nil { - return Type{}, err - } - return makeDynamicArrayType(arrayArgType), nil - case strings.HasSuffix(str, "]"): - stringMatches := staticArrayRegexp.FindStringSubmatch(str) - // match the string itself, array element type, then array length - if len(stringMatches) != 3 { - return Type{}, fmt.Errorf(`static array ill formated: "%s"`, str) - } - // guaranteed that the length of array is existing - arrayLengthStr := stringMatches[2] - // allowing only decimal static array length, with limit size to 2^16 - 1 - arrayLength, err := strconv.ParseUint(arrayLengthStr, 10, 16) - if err != nil { - return Type{}, err - } - // parse the array element type - arrayType, err := TypeOf(stringMatches[1]) - if err != nil { - return Type{}, err - } - return makeStaticArrayType(arrayType, uint16(arrayLength)), nil - case strings.HasPrefix(str, "uint"): - typeSize, err := strconv.ParseUint(str[4:], 10, 16) - if err != nil { - return Type{}, fmt.Errorf(`ill formed uint type: "%s"`, str) - } - return makeUintType(int(typeSize)) - case str == "byte": - return byteType, nil - case strings.HasPrefix(str, "ufixed"): - stringMatches := ufixedRegexp.FindStringSubmatch(str) - // match string itself, then type-bitSize, and type-precision - if len(stringMatches) != 3 { - return Type{}, fmt.Errorf(`ill formed ufixed type: "%s"`, str) - } - // guaranteed that there are 2 uint strings in ufixed string - ufixedSize, err := strconv.ParseUint(stringMatches[1], 10, 16) - if err != nil { - return Type{}, err - } - ufixedPrecision, err := strconv.ParseUint(stringMatches[2], 10, 16) - if err != nil { - return Type{}, err - } - return makeUfixedType(int(ufixedSize), int(ufixedPrecision)) - case str == "bool": - return boolType, nil - case str == "address": - return addressType, nil - case str == "string": - return stringType, nil - case len(str) >= 2 && str[0] == '(' && str[len(str)-1] == ')': - tupleContent, err := parseTupleContent(str[1 : len(str)-1]) - if err != nil { - return Type{}, err - } - tupleTypes := make([]Type, len(tupleContent)) - for i := 0; i < len(tupleContent); i++ { - ti, err := TypeOf(tupleContent[i]) - if err != nil { - return Type{}, err - } - tupleTypes[i] = ti - } - return MakeTupleType(tupleTypes) - default: - return Type{}, fmt.Errorf(`cannot convert the string "%s" to an ABI type`, str) - } -} - -// segment keeps track of the start and end of a segment in a string. -type segment struct{ left, right int } - -// parseTupleContent splits an ABI encoded string for tuple type into multiple sub-strings. -// Each sub-string represents a content type of the tuple type. -// The argument str is the content between parentheses of tuple, i.e. -// (...... str ......) -// ^ ^ -func parseTupleContent(str string) ([]string, error) { - // if the tuple type content is empty (which is also allowed) - // just return the empty string list - if len(str) == 0 { - return []string{}, nil - } - - // the following 2 checks want to make sure input string can be separated by comma - // with form: "...substr_0,...substr_1,...,...substr_k" - - // str should noe have leading/tailing comma - if strings.HasSuffix(str, ",") || strings.HasPrefix(str, ",") { - return []string{}, fmt.Errorf("parsing error: tuple content should not start with comma") - } - - // str should not have consecutive commas contained - if strings.Contains(str, ",,") { - return []string{}, fmt.Errorf("no consecutive commas") - } - - var parenSegmentRecord = make([]segment, 0) - var stack []int - - // get the most exterior parentheses segment (not overlapped by other parentheses) - // illustration: "*****,(*****),*****" => ["*****", "(*****)", "*****"] - // once iterate to left paren (, stack up by 1 in stack - // iterate to right paren ), pop 1 in stack - // if iterate to right paren ) with stack height 0, find a parenthesis segment "(******)" - for index, chr := range str { - if chr == '(' { - stack = append(stack, index) - } else if chr == ')' { - if len(stack) == 0 { - return []string{}, fmt.Errorf("unpaired parentheses: %s", str) - } - leftParenIndex := stack[len(stack)-1] - stack = stack[:len(stack)-1] - if len(stack) == 0 { - parenSegmentRecord = append(parenSegmentRecord, segment{ - left: leftParenIndex, - right: index, - }) - } - } - } - if len(stack) != 0 { - return []string{}, fmt.Errorf("unpaired parentheses: %s", str) - } - - // take out tuple-formed type str in tuple argument - strCopied := str - for i := len(parenSegmentRecord) - 1; i >= 0; i-- { - parenSeg := parenSegmentRecord[i] - strCopied = strCopied[:parenSeg.left] + strCopied[parenSeg.right+1:] - } - - // split the string without parenthesis segments - tupleStrSegs := strings.Split(strCopied, ",") - - // the empty strings are placeholders for parenthesis segments - // put the parenthesis segments back into segment list - parenSegCount := 0 - for index, segStr := range tupleStrSegs { - if segStr == "" { - parenSeg := parenSegmentRecord[parenSegCount] - tupleStrSegs[index] = str[parenSeg.left : parenSeg.right+1] - parenSegCount++ - } - } - - return tupleStrSegs, nil -} - -// makeUintType makes `Uint` ABI type by taking a type bitSize argument. -// The range of type bitSize is [8, 512] and type bitSize % 8 == 0. -func makeUintType(typeSize int) (Type, error) { - if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { - return Type{}, fmt.Errorf("unsupported uint type bitSize: %d", typeSize) - } - return Type{ - abiTypeID: Uint, - bitSize: uint16(typeSize), - }, nil -} - -var ( - // byteType is ABI type constant for byte - byteType = Type{abiTypeID: Byte} - - // boolType is ABI type constant for bool - boolType = Type{abiTypeID: Bool} - - // addressType is ABI type constant for address - addressType = Type{abiTypeID: Address} - - // stringType is ABI type constant for string - stringType = Type{abiTypeID: String} -) - -// makeUfixedType makes `UFixed` ABI type by taking type bitSize and type precision as arguments. -// The range of type bitSize is [8, 512] and type bitSize % 8 == 0. -// The range of type precision is [1, 160]. -func makeUfixedType(typeSize int, typePrecision int) (Type, error) { - if typeSize%8 != 0 || typeSize < 8 || typeSize > 512 { - return Type{}, fmt.Errorf("unsupported ufixed type bitSize: %d", typeSize) - } - if typePrecision > 160 || typePrecision < 1 { - return Type{}, fmt.Errorf("unsupported ufixed type precision: %d", typePrecision) - } - return Type{ - abiTypeID: Ufixed, - bitSize: uint16(typeSize), - precision: uint16(typePrecision), - }, nil -} - -// makeStaticArrayType makes static length array ABI type by taking -// array element type and array length as arguments. -func makeStaticArrayType(argumentType Type, arrayLength uint16) Type { - return Type{ - abiTypeID: ArrayStatic, - childTypes: []Type{argumentType}, - staticLength: arrayLength, - } -} - -// makeDynamicArrayType makes dynamic length array by taking array element type as argument. -func makeDynamicArrayType(argumentType Type) Type { - return Type{ - abiTypeID: ArrayDynamic, - childTypes: []Type{argumentType}, - } -} - -// MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument. -func MakeTupleType(argumentTypes []Type) (Type, error) { - if len(argumentTypes) >= math.MaxUint16 { - return Type{}, fmt.Errorf("tuple type child type number larger than maximum uint16 error") - } - return Type{ - abiTypeID: Tuple, - childTypes: argumentTypes, - staticLength: uint16(len(argumentTypes)), - }, nil -} - -// Equal method decides the equality of two types: t == t0. -func (t Type) Equal(t0 Type) bool { - if t.abiTypeID != t0.abiTypeID { - return false - } - if t.precision != t0.precision || t.bitSize != t0.bitSize { - return false - } - if t.staticLength != t0.staticLength { - return false - } - if len(t.childTypes) != len(t0.childTypes) { - return false - } - for i := 0; i < len(t.childTypes); i++ { - if !t.childTypes[i].Equal(t0.childTypes[i]) { - return false - } - } - - return true -} - -// IsDynamic method decides if an ABI type is dynamic or static. -func (t Type) IsDynamic() bool { - switch t.abiTypeID { - case ArrayDynamic, String: - return true - default: - for _, childT := range t.childTypes { - if childT.IsDynamic() { - return true - } - } - return false - } -} - -// Assume that the current index on the list of type is an ABI bool type. -// It returns the difference between the current index and the index of the furthest consecutive Bool type. -func findBoolLR(typeList []Type, index int, delta int) int { - until := 0 - for { - curr := index + delta*until - if typeList[curr].abiTypeID == Bool { - if curr != len(typeList)-1 && delta > 0 { - until++ - } else if curr > 0 && delta < 0 { - until++ - } else { - break - } - } else { - until-- - break - } - } - return until -} - -// ByteLen method calculates the byte length of a static ABI type. -func (t Type) ByteLen() (int, error) { - switch t.abiTypeID { - case Address: - return addressByteSize, nil - case Byte: - return singleByteSize, nil - case Uint, Ufixed: - return int(t.bitSize / 8), nil - case Bool: - return singleBoolSize, nil - case ArrayStatic: - if t.childTypes[0].abiTypeID == Bool { - byteLen := int(t.staticLength+7) / 8 - return byteLen, nil - } - elemByteLen, err := t.childTypes[0].ByteLen() - if err != nil { - return -1, err - } - return int(t.staticLength) * elemByteLen, nil - case Tuple: - size := 0 - for i := 0; i < len(t.childTypes); i++ { - if t.childTypes[i].abiTypeID == Bool { - // search after bool - after := findBoolLR(t.childTypes, i, 1) - // shift the index - i += after - // get number of bool - boolNum := after + 1 - size += (boolNum + 7) / 8 - } else { - childByteSize, err := t.childTypes[i].ByteLen() - if err != nil { - return -1, err - } - size += childByteSize - } - } - return size, nil - default: - return -1, fmt.Errorf("%s is a dynamic type", t.String()) - } -} - -// AnyTransactionType is the ABI argument type string for a nonspecific transaction argument -const AnyTransactionType = "txn" - -// IsTransactionType checks if a type string represents a transaction type -// argument, such as "txn", "pay", "keyreg", etc. -func IsTransactionType(s string) bool { - switch s { - case AnyTransactionType, "pay", "keyreg", "acfg", "axfer", "afrz", "appl": - return true - default: - return false - } -} - -// AccountReferenceType is the ABI argument type string for account references -const AccountReferenceType = "account" - -// AssetReferenceType is the ABI argument type string for asset references -const AssetReferenceType = "asset" - -// ApplicationReferenceType is the ABI argument type string for application references -const ApplicationReferenceType = "application" - -// IsReferenceType checks if a type string represents a reference type argument, -// such as "account", "asset", or "application". -func IsReferenceType(s string) bool { - switch s { - case AccountReferenceType, AssetReferenceType, ApplicationReferenceType: - return true - default: - return false - } -} - -// VoidReturnType is the ABI return type string for a method that does not return any value -const VoidReturnType = "void" diff --git a/data/abi/abi_type_test.go b/data/abi/abi_type_test.go deleted file mode 100644 index fb7c7e9025..0000000000 --- a/data/abi/abi_type_test.go +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package abi - -import ( - "fmt" - "math/rand" - "strconv" - "strings" - "testing" - "time" - - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" -) - -func TestMakeTypeValid(t *testing.T) { - partitiontest.PartitionTest(t) - // uint - for i := 8; i <= 512; i += 8 { - uintType, err := makeUintType(i) - require.NoError(t, err, "make uint type in valid space should not return error") - expected := "uint" + strconv.Itoa(i) - actual := uintType.String() - require.Equal(t, expected, actual, "makeUintType: expected %s, actual %s", expected, actual) - } - // ufixed - for i := 8; i <= 512; i += 8 { - for j := 1; j <= 160; j++ { - ufixedType, err := makeUfixedType(i, 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, - "TypeOf 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: boolType, testType: "bool", expected: "bool"}, - {input: stringType, testType: "string", expected: "string"}, - {input: addressType, testType: "address", expected: "address"}, - {input: byteType, testType: "byte", expected: "byte"}, - // dynamic array - { - input: makeDynamicArrayType( - Type{ - abiTypeID: Uint, - bitSize: uint16(32), - }, - ), - testType: "dynamic array", - expected: "uint32[]", - }, - { - input: makeDynamicArrayType( - makeDynamicArrayType( - byteType, - ), - ), - testType: "dynamic array", - expected: "byte[][]", - }, - { - input: makeStaticArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: uint16(128), - precision: uint16(10), - }, - uint16(100), - ), - testType: "static array", - expected: "ufixed128x10[100]", - }, - { - input: makeStaticArrayType( - makeStaticArrayType( - boolType, - uint16(128), - ), - uint16(256), - ), - testType: "static array", - expected: "bool[128][256]", - }, - // tuple type - { - input: Type{ - abiTypeID: Tuple, - childTypes: []Type{ - { - abiTypeID: Uint, - bitSize: uint16(32), - }, - { - abiTypeID: Tuple, - childTypes: []Type{ - addressType, - byteType, - makeStaticArrayType(boolType, uint16(10)), - makeDynamicArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: uint16(256), - precision: uint16(10), - }, - ), - }, - staticLength: 4, - }, - makeDynamicArrayType(byteType), - }, - staticLength: 3, - }, - testType: "tuple type", - expected: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", - }, - } - for _, testcase := range testcases { - t.Run(fmt.Sprintf("MakeType test %s", testcase.testType), func(t *testing.T) { - actual := testcase.input.String() - require.Equal(t, testcase.expected, actual, - "MakeType: expected %s, actual %s", testcase.expected, actual) - }) - } -} - -func TestMakeTypeInvalid(t *testing.T) { - partitiontest.PartitionTest(t) - // uint - for i := 0; i <= 1000; i++ { - randInput := rand.Uint32() % (1 << 16) - for randInput%8 == 0 && randInput <= 512 && randInput >= 8 { - randInput = rand.Uint32() % (1 << 16) - } - // note: if a var mod 8 = 0 (or not) in uint32, then it should mod 8 = 0 (or not) in uint16. - _, err := makeUintType(int(randInput)) - require.Error(t, err, "makeUintType: should throw error on bitSize input %d", uint16(randInput)) - } - // ufixed - for i := 0; i <= 10000; i++ { - randSize := rand.Uint64() % (1 << 16) - for randSize%8 == 0 && randSize <= 512 && randSize >= 8 { - randSize = rand.Uint64() % (1 << 16) - } - randPrecision := rand.Uint32() - for randPrecision >= 1 && randPrecision <= 160 { - randPrecision = rand.Uint32() - } - _, err := makeUfixedType(int(randSize), int(randPrecision)) - require.Error(t, err, "makeUfixedType: should throw error on bitSize %d, precision %d", randSize, randPrecision) - } -} - -func TestTypeFromStringValid(t *testing.T) { - partitiontest.PartitionTest(t) - // uint - for i := 8; i <= 512; i += 8 { - expected, err := makeUintType(i) - require.NoError(t, err, "make uint type in valid space should not return error") - actual, err := TypeOf(expected.String()) - require.NoError(t, err, "TypeOf: uint parsing error: %s", expected.String()) - require.Equal(t, expected, actual, - "TypeOf: expected %s, actual %s", expected.String(), actual.String()) - } - // ufixed - for i := 8; i <= 512; i += 8 { - for j := 1; j <= 160; j++ { - expected, err := makeUfixedType(i, j) - require.NoError(t, err, "make ufixed type in valid space should not return error") - actual, err := TypeOf("ufixed" + strconv.Itoa(i) + "x" + strconv.Itoa(j)) - require.NoError(t, err, "TypeOf ufixed parsing error: %s", expected.String()) - require.Equal(t, expected, actual, - "TypeOf ufixed: expected %s, actual %s", expected.String(), actual.String()) - } - } - var testcases = []struct { - input string - testType string - expected Type - }{ - {input: boolType.String(), testType: "bool", expected: boolType}, - {input: stringType.String(), testType: "string", expected: stringType}, - {input: addressType.String(), testType: "address", expected: addressType}, - {input: byteType.String(), testType: "byte", expected: byteType}, - { - input: "uint256[]", - testType: "dynamic array", - expected: makeDynamicArrayType(Type{abiTypeID: Uint, bitSize: 256}), - }, - { - input: "ufixed256x64[]", - testType: "dynamic array", - expected: makeDynamicArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: 256, - precision: 64, - }, - ), - }, - { - input: "byte[][][][]", - testType: "dynamic array", - expected: makeDynamicArrayType( - makeDynamicArrayType( - makeDynamicArrayType( - makeDynamicArrayType( - byteType, - ), - ), - ), - ), - }, - // static array - { - input: "address[100]", - testType: "static array", - expected: makeStaticArrayType( - addressType, - uint16(100), - ), - }, - { - input: "uint64[][200]", - testType: "static array", - expected: makeStaticArrayType( - makeDynamicArrayType( - Type{abiTypeID: Uint, bitSize: uint16(64)}, - ), - uint16(200), - ), - }, - // tuple type - { - input: "()", - testType: "tuple type", - expected: Type{ - abiTypeID: Tuple, - childTypes: []Type{}, - staticLength: 0, - }, - }, - { - input: "(uint32,(address,byte,bool[10],ufixed256x10[]),byte[])", - testType: "tuple type", - expected: Type{ - abiTypeID: Tuple, - childTypes: []Type{ - { - abiTypeID: Uint, - bitSize: uint16(32), - }, - { - abiTypeID: Tuple, - childTypes: []Type{ - addressType, - byteType, - makeStaticArrayType(boolType, uint16(10)), - makeDynamicArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: uint16(256), - precision: uint16(10), - }, - ), - }, - staticLength: 4, - }, - makeDynamicArrayType(byteType), - }, - staticLength: 3, - }, - }, - { - input: "(uint32,(address,byte,bool[10],(ufixed256x10[])))", - testType: "tuple type", - expected: Type{ - abiTypeID: Tuple, - childTypes: []Type{ - { - abiTypeID: Uint, - bitSize: uint16(32), - }, - { - abiTypeID: Tuple, - childTypes: []Type{ - addressType, - byteType, - makeStaticArrayType(boolType, uint16(10)), - { - abiTypeID: Tuple, - childTypes: []Type{ - makeDynamicArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: uint16(256), - precision: uint16(10), - }, - ), - }, - staticLength: 1, - }, - }, - staticLength: 4, - }, - }, - staticLength: 2, - }, - }, - { - input: "((uint32),(address,(byte,bool[10],ufixed256x10[])))", - testType: "tuple type", - expected: Type{ - abiTypeID: Tuple, - childTypes: []Type{ - { - abiTypeID: Tuple, - childTypes: []Type{ - { - abiTypeID: Uint, - bitSize: uint16(32), - }, - }, - staticLength: 1, - }, - { - abiTypeID: Tuple, - childTypes: []Type{ - addressType, - { - abiTypeID: Tuple, - childTypes: []Type{ - byteType, - makeStaticArrayType(boolType, uint16(10)), - makeDynamicArrayType( - Type{ - abiTypeID: Ufixed, - bitSize: uint16(256), - precision: uint16(10), - }, - ), - }, - staticLength: 3, - }, - }, - staticLength: 2, - }, - }, - staticLength: 2, - }, - }, - } - for _, testcase := range testcases { - t.Run(fmt.Sprintf("TypeOf test %s", testcase.testType), func(t *testing.T) { - actual, err := TypeOf(testcase.input) - require.NoError(t, err, "TypeOf %s parsing error", testcase.testType) - require.Equal(t, testcase.expected, actual, "TestFromString %s: expected %s, actual %s", - testcase.testType, testcase.expected.String(), actual.String()) - }) - } -} - -func TestTypeFromStringInvalid(t *testing.T) { - partitiontest.PartitionTest(t) - for i := 0; i <= 1000; i++ { - randSize := rand.Uint64() - for randSize%8 == 0 && randSize <= 512 && randSize >= 8 { - randSize = rand.Uint64() - } - errorInput := "uint" + strconv.FormatUint(randSize, 10) - _, err := TypeOf(errorInput) - require.Error(t, err, "makeUintType: should throw error on bitSize input %d", randSize) - } - for i := 0; i <= 10000; i++ { - randSize := rand.Uint64() - for randSize%8 == 0 && randSize <= 512 && randSize >= 8 { - randSize = rand.Uint64() - } - randPrecision := rand.Uint64() - for randPrecision >= 1 && randPrecision <= 160 { - randPrecision = rand.Uint64() - } - errorInput := "ufixed" + strconv.FormatUint(randSize, 10) + "x" + strconv.FormatUint(randPrecision, 10) - _, err := TypeOf(errorInput) - require.Error(t, err, "makeUintType: should throw error on bitSize input %d", randSize) - } - var testcases = []string{ - // uint - "uint123x345", - "uint 128", - "uint8 ", - "uint!8", - "uint[32]", - "uint-893", - "uint#120\\", - // ufixed - "ufixed000000000016x0000010", - "ufixed123x345", - "ufixed 128 x 100", - "ufixed64x10 ", - "ufixed!8x2 ", - "ufixed[32]x16", - "ufixed-64x+100", - "ufixed16x+12", - // dynamic array - "uint256 []", - "byte[] ", - "[][][]", - "stuff[]", - // static array - "ufixed32x10[0]", - "byte[10 ]", - "uint64[0x21]", - // tuple - "(ufixed128x10))", - "(,uint128,byte[])", - "(address,ufixed64x5,)", - "(byte[16],somethingwrong)", - "( )", - "((uint32)", - "(byte,,byte)", - "((byte),,(byte))", - } - for _, testcase := range testcases { - t.Run(fmt.Sprintf("TypeOf dynamic array test %s", testcase), func(t *testing.T) { - _, err := TypeOf(testcase) - require.Error(t, err, "%s should throw error", testcase) - }) - } -} - -func generateTupleType(baseTypes []Type, tupleTypes []Type) Type { - if len(baseTypes) == 0 && len(tupleTypes) == 0 { - panic("should not pass all nil arrays into generateTupleType") - } - tupleLen := 0 - for tupleLen == 0 { - tupleLen = rand.Intn(20) - } - resultTypes := make([]Type, tupleLen) - for i := 0; i < tupleLen; i++ { - baseOrTuple := rand.Intn(5) - if baseOrTuple == 1 && len(tupleTypes) > 0 { - resultTypes[i] = tupleTypes[rand.Intn(len(tupleTypes))] - } else { - resultTypes[i] = baseTypes[rand.Intn(len(baseTypes))] - } - } - return Type{abiTypeID: Tuple, childTypes: resultTypes, staticLength: uint16(tupleLen)} -} - -func TestTypeMISC(t *testing.T) { - partitiontest.PartitionTest(t) - rand.Seed(time.Now().Unix()) - - var testpool = []Type{ - boolType, - addressType, - stringType, - byteType, - } - for i := 8; i <= 512; i += 8 { - uintT, err := makeUintType(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(i, j) - require.NoError(t, err, "make ufixed type error: bitSize %d, precision %d", i, j) - testpool = append(testpool, ufixedT) - } - } - for _, testcase := range testpool { - testpool = append(testpool, makeDynamicArrayType(testcase)) - testpool = append(testpool, makeStaticArrayType(testcase, 10)) - testpool = append(testpool, makeStaticArrayType(testcase, 20)) - } - - for _, testcase := range testpool { - require.True(t, testcase.Equal(testcase), "test type self equal error") - } - baseTestCount := 0 - for baseTestCount < 1000 { - index0 := rand.Intn(len(testpool)) - index1 := rand.Intn(len(testpool)) - if index0 == index1 { - continue - } - require.False(t, testpool[index0].Equal(testpool[index1]), - "test type not equal error\n%s\n%s", - testpool[index0].String(), testpool[index1].String()) - baseTestCount++ - } - - testpoolTuple := make([]Type, 0) - for i := 0; i < 100; i++ { - testpoolTuple = append(testpoolTuple, generateTupleType(testpool, testpoolTuple)) - } - for _, testcaseTuple := range testpoolTuple { - require.True(t, testcaseTuple.Equal(testcaseTuple), "test type tuple equal error") - } - - tupleTestCount := 0 - for tupleTestCount < 100 { - index0 := rand.Intn(len(testpoolTuple)) - index1 := rand.Intn(len(testpoolTuple)) - if testpoolTuple[index0].String() == testpoolTuple[index1].String() { - continue - } - require.False(t, testpoolTuple[index0].Equal(testpoolTuple[index1]), - "test type tuple not equal error\n%s\n%s", - testpoolTuple[index0].String(), testpoolTuple[index1].String()) - tupleTestCount++ - } - - testpool = append(testpool, testpoolTuple...) - isDynamicCount := 0 - for isDynamicCount < 100 { - index := rand.Intn(len(testpool)) - isDynamicArr := strings.Contains(testpool[index].String(), "[]") - isDynamicStr := strings.Contains(testpool[index].String(), "string") - require.Equal(t, isDynamicArr || isDynamicStr, testpool[index].IsDynamic(), - "test type isDynamic error\n%s", testpool[index].String()) - isDynamicCount++ - } - - addressByteLen, err := addressType.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 := byteType.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 := boolType.ByteLen() - require.NoError(t, err, "bool type bytelen should be 1") - require.Equal(t, 1, boolByteLen, "bool type bytelen should be 1") - - byteLenTestCount := 0 - for byteLenTestCount < 100 { - index := rand.Intn(len(testpool)) - testType := testpool[index] - byteLen, err := testType.ByteLen() - if testType.IsDynamic() { - require.Error(t, err, "byteLen test error on %s dynamic type, should have error", - testType.String()) - } else { - require.NoError(t, err, "byteLen test error on %s dynamic type, should not have error") - if testType.abiTypeID == Tuple { - sizeSum := 0 - for i := 0; i < len(testType.childTypes); i++ { - if testType.childTypes[i].abiTypeID == Bool { - // search previous bool - before := findBoolLR(testType.childTypes, i, -1) - // search after bool - after := findBoolLR(testType.childTypes, i, 1) - // append to heads and tails - require.True(t, before%8 == 0, "expected tuple bool compact by 8") - if after > 7 { - after = 7 - } - i += after - sizeSum++ - } else { - childByteSize, err := testType.childTypes[i].ByteLen() - require.NoError(t, err, "byteLen not expected to fail on tuple child type") - sizeSum += childByteSize - } - } - - require.Equal(t, sizeSum, byteLen, - "%s do not match calculated byte length %d", testType.String(), sizeSum) - } else if testType.abiTypeID == ArrayStatic { - if testType.childTypes[0].abiTypeID == Bool { - expected := testType.staticLength / 8 - if testType.staticLength%8 != 0 { - expected++ - } - actual, err := testType.ByteLen() - require.NoError(t, err, "%s should not return error on byteLen test") - require.Equal(t, int(expected), actual, "%s do not match calculated byte length %d", - testType.String(), expected) - } else { - childSize, err := testType.childTypes[0].ByteLen() - require.NoError(t, err, "%s should not return error on byteLen test", testType.childTypes[0].String()) - expected := childSize * int(testType.staticLength) - require.Equal(t, expected, byteLen, - "%s do not match calculated byte length %d", testType.String(), expected) - } - } - } - byteLenTestCount++ - } -} diff --git a/data/basics/address.go b/data/basics/address.go index 5eed1c5121..412b7bf75b 100644 --- a/data/basics/address.go +++ b/data/basics/address.go @@ -24,23 +24,6 @@ import ( "github.com/algorand/go-algorand/crypto" ) -// NOTE: Another (partial) implementation of `basics.Address` is in `data/abi`. -// The reason of not using this `Address` in `data/abi` is that: -// - `data/basics` has C dependencies (`go-algorand/crypto`) -// - `go-algorand-sdk` has dependency to `go-algorand` for `ABI` -// - if `go-algorand`'s ABI uses `basics.Address`, then it would be -// impossible to up the version of `go-algorand` in `go-algorand-sdk` - -// This is discussed in: -// - ISSUE https://github.com/algorand/go-algorand/issues/3355 -// - PR https://github.com/algorand/go-algorand/pull/3375 - -// There are two solutions: -// - One is to refactoring `crypto.Digest`, `crypto.Hash` and `basics.Address` -// into packages that does not need `libsodium` crypto dependency -// - The other is wrapping `libsodium` in a driver interface to make crypto -// package importable (even if `libsodium` does not exist) - type ( // Address is a unique identifier corresponding to ownership of money Address crypto.Digest diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index f99c480834..6a7144d9bc 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -31,7 +31,7 @@ import ( "strconv" "strings" - "github.com/algorand/go-algorand/data/abi" + "github.com/algorand/avm-abi/abi" "github.com/algorand/go-algorand/data/basics" ) diff --git a/go.mod b/go.mod index dd46477e86..f20b25dc6d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/algorand/go-algorand go 1.17 require ( + github.com/algorand/avm-abi v0.1.0 github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 github.com/algorand/go-codec/codec v1.1.8 github.com/algorand/go-deadlock v0.2.2 @@ -12,7 +13,6 @@ require ( github.com/algorand/oapi-codegen v1.3.7 github.com/algorand/websocket v1.4.5 github.com/aws/aws-sdk-go v1.16.5 - github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e github.com/consensys/gnark-crypto v0.7.0 github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 github.com/dchest/siphash v1.2.1 @@ -39,6 +39,7 @@ require ( ) require ( + github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e // indirect github.com/cpuguy83/go-md2man v1.0.8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index 0537d11015..58fbbdb98c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/algorand/avm-abi v0.1.0 h1:znZFQXpSUVYz37vXbaH5OZG2VK4snTyXwnc/tV9CVr4= +github.com/algorand/avm-abi v0.1.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 h1:nwYN+GQ7Z5OOfZwqBO1ma7DSlP7S1YrKWICOyjkwqrc= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4=