diff options
Diffstat (limited to 'data/abi/abi_encode_test.go')
-rw-r--r-- | data/abi/abi_encode_test.go | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go new file mode 100644 index 000000000..c585564c6 --- /dev/null +++ b/data/abi/abi_encode_test.go @@ -0,0 +1,1003 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see <https://www.gnu.org/licenses/>. + +package abi + +import ( + "crypto/rand" + "encoding/binary" + "math/big" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/chrismcguire/gobberish" + "github.com/stretchr/testify/require" +) + +func TestEncodeValid(t *testing.T) { + partitiontest.PartitionTest(t) + + // encoding test for uint type, iterating through all uint sizes + // randomly pick 1000 valid uint values and check if encoded value match with expected + for intSize := 8; intSize <= 512; intSize += 8 { + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize)) + uintType, err := makeUintType(intSize) + require.NoError(t, err, "make uint type fail") + + for i := 0; i < 1000; i++ { + randomInt, err := rand.Int(rand.Reader, upperLimit) + require.NoError(t, err, "cryptographic random int init fail") + + randomIntByte := randomInt.Bytes() + expected := make([]byte, intSize/8-len(randomIntByte)) + expected = append(expected, randomIntByte...) + + 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<bitSize> can contain max uint value (2^bitSize - 1) + largest := big.NewInt(0).Add( + upperLimit, + big.NewInt(1).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 := 8; size <= 512; size += 8 { + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + largest := big.NewInt(0).Add( + upperLimit, + big.NewInt(1).Neg(big.NewInt(1)), + ) + for precision := 1; precision <= 160; precision++ { + typeUfixed, err := makeUfixedType(size, precision) + require.NoError(t, err, "make ufixed type fail") + + for i := 0; i < 10; 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") + + randomBytes := randomInt.Bytes() + buffer := make([]byte, size/8-len(randomBytes)) + buffer = append(buffer, randomBytes...) + require.Equal(t, buffer, encodedUfixed, "encode ufixed not match with expected") + } + // (2^[bitSize] - 1) / (10^[precision]) test + 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 := big.NewInt(0).Lsh(big.NewInt(1), 256) + for i := 0; i < 1000; i++ { + randomAddrInt, err := rand.Int(rand.Reader, upperLimit) + require.NoError(t, err, "cryptographic random int init fail") + + rand256Bytes := randomAddrInt.Bytes() + addrBytesExpected := make([]byte, 32-len(rand256Bytes)) + addrBytesExpected = append(addrBytesExpected, rand256Bytes...) + + 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 < 2; 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 < (1 << 8); 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 <= 100; length++ { + for i := 0; i < 10; 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 := 8; intSize <= 512; intSize += 8 { + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(intSize)) + uintType, err := makeUintType(intSize) + require.NoError(t, err, "make uint type failure") + for i := 0; i < 1000; 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 := 8; size <= 512; size += 8 { + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), uint(size)) + for precision := 1; precision <= 160; precision++ { + ufixedType, err := makeUfixedType(size, precision) + require.NoError(t, err, "make ufixed type failure") + for i := 0; i < 10; 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 1000 tests + // address is type alias of byte[32], we generate address value with random 256 bit big int values + // we make the expected address value and decode the encoding of expected, check if they match + upperLimit := big.NewInt(0).Lsh(big.NewInt(1), 256) + for i := 0; i < 1000; i++ { + randomAddrInt, err := rand.Int(rand.Reader, upperLimit) + require.NoError(t, err, "cryptographic random int init fail") + + addressBytes := randomAddrInt.Bytes() + expected := make([]byte, 32-len(addressBytes)) + expected = append(expected, addressBytes...) + + 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 < (1 << 8); 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 100 + // randomly take 10 utf-8 strings to make ABI string values + // decode the encoded expected value and check if they match + for length := 1; length <= 100; length++ { + for i := 0; i < 10; i++ { + 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") + encodedValue, err := abiType.Encode(testObj.value) + require.NoError(t, err, "failure to encode value") + actual, err := abiType.Decode(encodedValue) + require.NoError(t, err, "failure to decode value") + require.Equal(t, testObj.value, actual, "decoded value not equal to expected") + jsonEncodedValue, err := abiType.MarshalToJSON(testObj.value) + require.NoError(t, err, "failure to encode value to JSON type") + jsonActual, err := abiType.UnmarshalFromJSON(jsonEncodedValue) + require.NoError(t, err, "failure to decode JSON value back") + require.Equal(t, testObj.value, jsonActual, "decode JSON value not equal to expected") + } +} + +func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) { + (*pool)[Uint] = make([]testUnit, 200*64) + (*pool)[Ufixed] = make([]testUnit, 160*64) + + uintIndex := 0 + ufixedIndex := 0 + + for bitSize := 8; bitSize <= 512; bitSize += 8 { + 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 < 200; 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 <= 160; 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, 1<<8) + for i := 0; i < (1 << 8); i++ { + (*pool)[Byte][i] = testUnit{serializedType: byteType.String(), value: byte(i)} + } + categorySelfRoundTripTest(t, (*pool)[Byte]) + + (*pool)[Bool] = make([]testUnit, 2) + (*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), 256) + (*pool)[Address] = make([]testUnit, 300) + for i := 0; i < 300; i++ { + randAddrVal, err := rand.Int(rand.Reader, maxAddress) + require.NoError(t, err, "generate random value for address, should be no error") + addrBytes := randAddrVal.Bytes() + remainBytes := make([]byte, 32-len(addrBytes)) + addrBytes = append(remainBytes, addrBytes...) + (*pool)[Address][i] = testUnit{serializedType: addressType.String(), value: addrBytes} + } + categorySelfRoundTripTest(t, (*pool)[Address]) + + (*pool)[String] = make([]testUnit, 400) + stringIndex := 0 + for length := 1; length <= 100; length++ { + for i := 0; i < 4; 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 += 200 { + takeSomeFromCategoryAndGenerateArray(t, Uint, intIndex, 20, pool) + } + takeSomeFromCategoryAndGenerateArray(t, Byte, 0, 20, pool) + takeSomeFromCategoryAndGenerateArray(t, Address, 0, 20, pool) + takeSomeFromCategoryAndGenerateArray(t, String, 0, 20, pool) + takeSomeFromCategoryAndGenerateArray(t, Bool, 0, 20, pool) + + categorySelfRoundTripTest(t, (*pool)[ArrayStatic]) + categorySelfRoundTripTest(t, (*pool)[ArrayDynamic]) +} + +func addTupleRandomValues(t *testing.T, slotRange BaseType, pool *map[BaseType][]testUnit) { + for i := 0; i < 100; i++ { + tupleLenBig, err := rand.Int(rand.Reader, big.NewInt(20)) + 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]) +} |