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()) } }) } }