status-go/services/typeddata/hash_test.go

312 lines
8.9 KiB
Go

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