status-im/status-go

View on GitHub
services/typeddata/hash_test.go

Summary

Maintainability
A
0 mins
Test Coverage
package typeddata

import (
    "encoding/json"
    "fmt"
    "math/big"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"

    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
)

func TestTypeString(t *testing.T) {
    type testCase struct {
        description string
        typeString  string
        types       Types
        target      string
    }
    for _, tc := range []testCase{
        {
            "WithoutDeps",
            "Person(string name,address wallet)",
            Types{"Person": []Field{{Name: "name", Type: "string"}, {Name: "wallet", Type: "address"}}},
            "Person",
        },
        {
            "SingleDep",
            "Mail(Person from,Person to)Person(string name,address wallet)",
            Types{
                "Person": []Field{{Name: "name", Type: "string"}, {Name: "wallet", Type: "address"}},
                "Mail":   []Field{{Name: "from", Type: "Person"}, {Name: "to", Type: "Person"}},
            },
            "Mail",
        },
        {
            "DepsOrdered",
            "Z(A a,B b)A(string name)B(string name)",
            Types{
                "A": []Field{{Name: "name", Type: "string"}},
                "B": []Field{{Name: "name", Type: "string"}},
                "Z": []Field{{Name: "a", Type: "A"}, {Name: "b", Type: "B"}},
            },
            "Z",
        },
        {
            "RecursiveDepsIgnored",
            "Z(A a)A(Z z)",
            Types{
                "A": []Field{{Name: "z", Type: "Z"}},
                "Z": []Field{{Name: "a", Type: "A"}},
            },
            "Z",
        },
    } {
        tc := tc
        t.Run(tc.description, func(t *testing.T) {
            require.Equal(t, tc.typeString, typeString(tc.target, tc.types))
        })
    }
}

func TestEncodeData(t *testing.T) {
    type testCase struct {
        description string
        message     map[string]json.RawMessage
        types       Types
        target      string
        result      func(testCase) common.Hash
    }

    bytes32, _ := abi.NewType("bytes32", "", nil)
    addr, _ := abi.NewType("address", "", nil)
    boolT, _ := abi.NewType("bool", "", nil)

    for _, tc := range []testCase{
        {
            "HexAddressConvertedToBytes",
            map[string]json.RawMessage{"wallet": json.RawMessage(`"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"`)},
            Types{"A": []Field{{Name: "wallet", Type: "address"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: addr}}
                typehash := typeHash(tc.target, tc.types)
                var data common.Address
                assert.NoError(t, json.Unmarshal(tc.message["wallet"], &data))
                packed, _ := args.Pack(typehash, data)
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "StringHashed",
            map[string]json.RawMessage{"name": json.RawMessage(`"AAA"`)},
            Types{"A": []Field{{Name: "name", Type: "string"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: bytes32}}
                typehash := typeHash(tc.target, tc.types)
                var data string
                assert.NoError(t, json.Unmarshal(tc.message["name"], &data))
                packed, _ := args.Pack(typehash, crypto.Keccak256Hash([]byte(data)))
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "BytesHashed",
            map[string]json.RawMessage{"name": json.RawMessage(`"0x010203"`)}, // []byte{1,2,3}
            Types{"A": []Field{{Name: "name", Type: "bytes"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: bytes32}}
                typehash := typeHash(tc.target, tc.types)
                var data hexutil.Bytes
                assert.NoError(t, json.Unmarshal(tc.message["name"], &data))
                packed, _ := args.Pack(typehash, crypto.Keccak256Hash(data))
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "FixedBytesAsIs",
            map[string]json.RawMessage{"name": json.RawMessage(`"0x010203"`)}, // []byte{1,2,3}
            Types{"A": []Field{{Name: "name", Type: "bytes32"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: bytes32}}
                typehash := typeHash(tc.target, tc.types)
                var data hexutil.Bytes
                assert.NoError(t, json.Unmarshal(tc.message["name"], &data))
                rst := [32]byte{}
                copy(rst[:], data)
                packed, _ := args.Pack(typehash, rst)
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "BoolAsIs",
            map[string]json.RawMessage{"flag": json.RawMessage("true")},
            Types{"A": []Field{{Name: "flag", Type: "bool"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: boolT}}
                typehash := typeHash(tc.target, tc.types)
                var data bool
                assert.NoError(t, json.Unmarshal(tc.message["flag"], &data))
                packed, _ := args.Pack(typehash, data)
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "Int32Uint32AsIs",
            map[string]json.RawMessage{"I": json.RawMessage("-10"), "UI": json.RawMessage("10")},
            Types{"A": []Field{{Name: "I", Type: "int32"}, {Name: "UI", Type: "uint32"}}},
            "A",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: int256Type}, {Type: int256Type}}
                typehash := typeHash(tc.target, tc.types)
                packed, _ := args.Pack(typehash, big.NewInt(-10), big.NewInt(10))
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "SignedUnsignedIntegersBiggerThen64",
            map[string]json.RawMessage{
                "i128":  json.RawMessage("1"),
                "i256":  json.RawMessage("1"),
                "ui128": json.RawMessage("1"),
                "ui256": json.RawMessage("1"),
            },
            Types{"A": []Field{
                {Name: "i128", Type: "int128"}, {Name: "i256", Type: "int256"},
                {Name: "ui128", Type: "uint128"}, {Name: "ui256", Type: "uint256"},
            }},
            "A",
            func(tc testCase) common.Hash {
                intBig, _ := abi.NewType("int128", "", nil)
                uintBig, _ := abi.NewType("uint128", "", nil)
                args := abi.Arguments{{Type: bytes32},
                    {Type: intBig}, {Type: intBig}, {Type: uintBig}, {Type: uintBig}}
                typehash := typeHash(tc.target, tc.types)
                val := big.NewInt(1)
                packed, _ := args.Pack(typehash, val, val, val, val)
                return crypto.Keccak256Hash(packed)
            },
        },
        {
            "CompositeTypesAreRecursivelyEncoded",
            map[string]json.RawMessage{"a": json.RawMessage(`{"name":"AAA"}`)},
            Types{"A": []Field{{Name: "name", Type: "string"}}, "Z": []Field{{Name: "a", Type: "A"}}},
            "Z",
            func(tc testCase) common.Hash {
                args := abi.Arguments{{Type: bytes32}, {Type: bytes32}}
                zhash := typeHash(tc.target, tc.types)
                ahash := typeHash("A", tc.types)
                var A map[string]string
                assert.NoError(t, json.Unmarshal(tc.message["a"], &A))
                apacked, _ := args.Pack(ahash, crypto.Keccak256Hash([]byte(A["name"])))
                packed, _ := args.Pack(zhash, crypto.Keccak256Hash(apacked))
                return crypto.Keccak256Hash(packed)
            },
        },
    } {
        tc := tc
        t.Run(tc.description, func(t *testing.T) {
            encoded, err := hashStruct(tc.target, tc.message, tc.types)
            require.NoError(t, err)
            require.Equal(t, tc.result(tc), encoded)
        })
    }
}

func TestEncodeDataErrors(t *testing.T) {
    type testCase struct {
        description string
        message     map[string]json.RawMessage
        types       Types
        target      string
    }

    for _, tc := range []testCase{
        {
            "FailedUnmxarshalAsAString",
            map[string]json.RawMessage{"a": json.RawMessage("1")},
            Types{"A": []Field{{Name: "name", Type: "string"}}},
            "A",
        },
        {
            "FailedUnmarshalToHexBytesToABytes",
            map[string]json.RawMessage{"a": {1, 2, 3}},
            Types{"A": []Field{{Name: "name", Type: "bytes"}}},
            "A",
        },
        {
            "CompositeTypeIsNotAnObject",
            map[string]json.RawMessage{"a": json.RawMessage(`"AAA"`)},
            Types{"A": []Field{{Name: "name", Type: "string"}}, "Z": []Field{{Name: "a", Type: "A"}}},
            "Z",
        },
        {
            "CompositeTypesFailed",
            map[string]json.RawMessage{"a": json.RawMessage(`{"name":10}`)},
            Types{"A": []Field{{Name: "name", Type: "string"}}, "Z": []Field{{Name: "a", Type: "A"}}},
            "Z",
        },
        {
            "ArraysNotSupported",
            map[string]json.RawMessage{"a": json.RawMessage("[1,2]")},
            Types{"A": []Field{{Name: "name", Type: "int8[2]"}}},
            "A",
        },
        {
            "SlicesNotSupported",
            map[string]json.RawMessage{"a": json.RawMessage("[1,2]")},
            Types{"A": []Field{{Name: "name", Type: "int[]"}}},
            "A",
        },
        {
            "FailedToUnmarshalInteger",
            map[string]json.RawMessage{"a": json.RawMessage("x00x")},
            Types{"A": []Field{{Name: "name", Type: "uint256"}}},
            "A",
        },
    } {
        tc := tc
        t.Run(tc.description, func(t *testing.T) {
            encoded, err := hashStruct(tc.target, tc.message, tc.types)
            require.Error(t, err)
            require.Equal(t, common.Hash{}, encoded)
        })
    }
}

func TestEncodeInt(t *testing.T) {
    example := new(big.Int).Exp(big.NewInt(2), big.NewInt(255), nil)
    for _, tc := range []struct {
        description string
        data        []byte
        expected    *big.Int
        err         error
    }{
        {
            description: "AsString",
            data:        []byte(example.String()),
            expected:    example,
        },
        {
            description: "WrappedIntoString",
            data:        []byte(fmt.Sprintf("\"%s\"", example.String())),
            expected:    example,
        },
        {
            description: "NotAnInteger",
            data:        []byte("\"xzy\""),
            err:         errNotInteger,
        },
    } {
        t.Run(tc.description, func(t *testing.T) {
            rst, _, err := toInt(Field{}, tc.data)
            if tc.err == nil {
                require.NoError(t, err)
                require.Equal(t, tc.expected, rst)
            } else {
                require.EqualError(t, err, tc.err.Error())
            }
        })
    }
}