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=