diff --git a/types/metadataV13.go b/types/metadataV13.go index 614f14005..f2f614be2 100644 --- a/types/metadataV13.go +++ b/types/metadataV13.go @@ -291,45 +291,14 @@ func (s StorageFunctionMetadataV13) Hashers() ([]hash.Hash, error) { return nil, fmt.Errorf("only NMaps have Hashers") } - var hashers []hash.Hash - if s.Type.IsMap { - hashers = make([]hash.Hash, 1) - mapHasher, err := s.Type.AsMap.Hasher.HashFunc() + hashers := make([]hash.Hash, len(s.Type.AsNMap.Hashers)) + for i, hasher := range s.Type.AsNMap.Hashers { + hasherFn, err := hasher.HashFunc() if err != nil { return nil, err } - hashers[0] = mapHasher - return hashers, nil + hashers[i] = hasherFn } - if s.Type.IsDoubleMap { - hashers = make([]hash.Hash, 2) - firstDoubleMapHasher, err := s.Type.AsDoubleMap.Hasher.HashFunc() - if err != nil { - return nil, err - } - hashers[0] = firstDoubleMapHasher - secondDoubleMapHasher, err := s.Type.AsDoubleMap.Key2Hasher.HashFunc() - if err != nil { - return nil, err - } - hashers[1] = secondDoubleMapHasher - return hashers, nil - } - if s.Type.IsNMap { - hashers = make([]hash.Hash, len(s.Type.AsNMap.Hashers)) - for i, hasher := range s.Type.AsNMap.Hashers { - hasherFn, err := hasher.HashFunc() - if err != nil { - return nil, err - } - hashers[i] = hasherFn - } - return hashers, nil - } - - hashers = make([]hash.Hash, 1) - hashers[0] = xxhash.New128(nil) - return hashers, nil } diff --git a/types/storage_key.go b/types/storage_key.go index 68f4a0e07..3cf747438 100644 --- a/types/storage_key.go +++ b/types/storage_key.go @@ -79,28 +79,37 @@ func CreateStorageKey(meta *Metadata, prefix, method string, args ...[]byte) (St } if entryMeta.IsNMap() { - return createKeyNMap(meta, method, prefix, validatedArgs, entryMeta) + hashers, err := entryMeta.Hashers() + if err != nil { + return nil, fmt.Errorf("unable to get hashers for %s nmap", method) + } + if len(hashers) != len(validatedArgs) { + return nil, fmt.Errorf("%s:%s is a nmap, therefore requires that number of arguments should "+ + "exactly match number of hashers in metadata. "+ + "Expected: %d, received: %d", prefix, method, len(hashers), len(validatedArgs)) + } + return createKeyNMap(method, prefix, validatedArgs, entryMeta) } if entryMeta.IsDoubleMap() { if len(validatedArgs) != 2 { - return nil, fmt.Errorf("%v is a double map, therefore requires precisely two arguments. "+ - "received: %d", method, len(validatedArgs)) + return nil, fmt.Errorf("%s:%s is a double map, therefore requires precisely two arguments. "+ + "received: %d", prefix, method, len(validatedArgs)) } return createKeyDoubleMap(meta, method, prefix, stringKey, validatedArgs[0], validatedArgs[1], entryMeta) } if entryMeta.IsMap() { if len(validatedArgs) != 1 { - return nil, fmt.Errorf("%v is a map, therefore requires precisely one argument. "+ - "received: %d", method, len(validatedArgs)) + return nil, fmt.Errorf("%s:%s is a map, therefore requires precisely one argument. "+ + "received: %d", prefix, method, len(validatedArgs)) } return createKey(meta, method, prefix, stringKey, validatedArgs[0], entryMeta) } if entryMeta.IsPlain() && len(validatedArgs) != 0 { - return nil, fmt.Errorf("%v is a plain key, therefore requires no argument. "+ - "received: %d", method, len(validatedArgs)) + return nil, fmt.Errorf("%s:%s is a plain key, therefore requires no argument. "+ + "received: %d", prefix, method, len(validatedArgs)) } return createKey(meta, method, prefix, stringKey, nil, entryMeta) @@ -131,22 +140,12 @@ func (s StorageKey) Hex() string { return fmt.Sprintf("%#x", s) } -func createKeyNMap(meta *Metadata, method, prefix string, args [][]byte, - entryMeta StorageEntryMetadata) (StorageKey, error) { - if !meta.IsMetadataV13 { - return nil, fmt.Errorf("storage n map is only supported in metadata version 13 or up") - } - +func createKeyNMap(method, prefix string, args [][]byte, entryMeta StorageEntryMetadata) (StorageKey, error) { hashers, err := entryMeta.Hashers() if err != nil { return nil, err } - if len(hashers) != len(args) { - return nil, fmt.Errorf("number of arguments should exactly match number of hashers in metadata. "+ - "Expected: %d, received: %d", len(hashers), len(args)) - } - key := createPrefixedKey(method, prefix) for i, arg := range args { @@ -163,9 +162,6 @@ func createKeyNMap(meta *Metadata, method, prefix string, args [][]byte, // createKeyDoubleMap creates a key for a DoubleMap type func createKeyDoubleMap(meta *Metadata, method, prefix string, stringKey, arg, arg2 []byte, entryMeta StorageEntryMetadata) (StorageKey, error) { - if arg == nil || arg2 == nil { - return nil, fmt.Errorf("%v is a DoubleMap and requires two arguments", method) - } hasher, err := entryMeta.Hasher() if err != nil { @@ -208,9 +204,6 @@ func createKeyDoubleMap(meta *Metadata, method, prefix string, stringKey, arg, a // createKey creates a key for either a map or a plain value func createKey(meta *Metadata, method, prefix string, stringKey, arg []byte, entryMeta StorageEntryMetadata) ( StorageKey, error) { - if entryMeta.IsMap() && arg == nil { - return nil, fmt.Errorf("%v is a Map and requires one argument", method) - } hasher, err := entryMeta.Hasher() if err != nil { diff --git a/types/storage_key_test.go b/types/storage_key_test.go index 5c223b599..04f190da1 100644 --- a/types/storage_key_test.go +++ b/types/storage_key_test.go @@ -17,6 +17,10 @@ package types_test import ( + "encoding/binary" + "github.com/centrifuge/go-substrate-rpc-client/v3/hash" + "github.com/centrifuge/go-substrate-rpc-client/v3/xxhash" + "strings" "testing" . "github.com/centrifuge/go-substrate-rpc-client/v3/types" @@ -27,10 +31,202 @@ const ( AlicePubKey = "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) +func TestCreateStorageKeyArgValidationForPlainKey(t *testing.T) { + m := ExamplaryMetadataV13 + + _, err := CreateStorageKey(m, "Timestamp", "Now") + assert.NoError(t, err) + + _, err = CreateStorageKey(m, "Timestamp", "Now", nil) + assert.NoError(t, err) + + _, err = CreateStorageKey(m, "Timestamp", "Now", nil, []byte{}) + assert.NoError(t, err) + + _, err = CreateStorageKey(m, "Timestamp", "Now", nil, []byte{0x01}) + assert.EqualError(t, err, "non-nil arguments cannot be preceded by nil arguments") + + _, err = CreateStorageKey(m, "Timestamp", "Now", []byte{0x01}) + assert.EqualError(t, err, "Timestamp:Now is a plain key, therefore requires no argument. received: 1") + + expectedKeyBuilder := strings.Builder{} + hexStr, err := Hex(xxhash.New128([]byte("Timestamp")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(hexStr) + hexStr, err = Hex(xxhash.New128([]byte("Now")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + + key, err := CreateStorageKey(m, "Timestamp", "Now") + assert.NoError(t, err) + hex, err := Hex(key) + assert.NoError(t, err) + assert.Equal(t, expectedKeyBuilder.String(), hex) +} + +func TestCreateStorageKeyArgValidationForMapKey(t *testing.T) { + m := ExamplaryMetadataV13 + + _, err := CreateStorageKey(m, "System", "Account") + assert.EqualError(t, err, "System:Account is a map, therefore requires precisely one argument. " + + "received: 0") + + _, err = CreateStorageKey(m, "System", "Account", nil) + assert.EqualError(t, err, "System:Account is a map, therefore requires precisely one argument. " + + "received: 0") + + _, err = CreateStorageKey(m, "System", "Account", nil, []byte{}) + assert.EqualError(t, err, "System:Account is a map, therefore requires precisely one argument. " + + "received: 0") + + _, err = CreateStorageKey(m, "System", "Account", nil, []byte{0x01}) + assert.EqualError(t, err, "non-nil arguments cannot be preceded by nil arguments") + + accountIdSerialized := MustHexDecodeString(AlicePubKey) + + // Build expected answer + expectedKeyBuilder := strings.Builder{} + hexStr, err := Hex(xxhash.New128([]byte("System")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(hexStr) + hexStr, err = Hex(xxhash.New128([]byte("Account")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + accountIdHasher, err := hash.NewBlake2b128Concat(nil) + assert.NoError(t, err) + _, err = accountIdHasher.Write(accountIdSerialized) + assert.NoError(t, err) + hexStr, err = Hex(accountIdHasher.Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + + key, err := CreateStorageKey(m, "System", "Account", accountIdSerialized) + assert.NoError(t, err) + hex, err := Hex(key) + assert.NoError(t, err) + assert.Equal(t, expectedKeyBuilder.String(), hex) +} + +func TestCreateStorageKeyArgValidationForDoubleMapKey(t *testing.T) { + m := ExamplaryMetadataV13 + + _, err := CreateStorageKey(m, "Staking", "ErasStakers") + assert.EqualError(t, err, "Staking:ErasStakers is a double map, therefore requires precisely two " + + "arguments. received: 0") + + _, err = CreateStorageKey(m, "Staking", "ErasStakers", nil) + assert.EqualError(t, err, "Staking:ErasStakers is a double map, therefore requires precisely two " + + "arguments. received: 0") + + _, err = CreateStorageKey(m, "Staking", "ErasStakers", nil, []byte{}) + assert.EqualError(t, err, "Staking:ErasStakers is a double map, therefore requires precisely two " + + "arguments. received: 0") + + _, err = CreateStorageKey(m, "Staking", "ErasStakers", nil, []byte{0x01}) + assert.EqualError(t, err, "non-nil arguments cannot be preceded by nil arguments") + + _, err = CreateStorageKey(m, "Staking", "ErasStakers", []byte{0x01}) + assert.EqualError(t, err, "Staking:ErasStakers is a double map, therefore requires precisely two " + + "arguments. received: 1") + + // Serialize EraIndex and AccountId + accountIdSerialized := MustHexDecodeString(AlicePubKey) + var eraIndex uint32 = 3 + eraIndexSerialized := make([]byte, 4) + binary.LittleEndian.PutUint32(eraIndexSerialized, eraIndex) + + // Build expected answer + expectedKeyBuilder := strings.Builder{} + hexStr, err := Hex(xxhash.New128([]byte("Staking")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(hexStr) + hexStr, err = Hex(xxhash.New128([]byte("ErasStakers")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + hexStr, err = Hex(xxhash.New64Concat(eraIndexSerialized).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + hexStr, err = Hex(xxhash.New64Concat(accountIdSerialized).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + + key, err := CreateStorageKey(m, "Staking", "ErasStakers", eraIndexSerialized, accountIdSerialized) + assert.NoError(t, err) + hex, err := Hex(key) + assert.NoError(t, err) + + assert.Equal(t, expectedKeyBuilder.String(), hex) +} + +func TestCreateStorageKeyArgValidationForNMapKey(t *testing.T) { + m := ExamplaryMetadataV13 + //"Assets", "Approvals", "AssetId(u32)", "AccountId", "AccountId" + + _, err := CreateStorageKey(m, "Assets", "Approvals") + assert.EqualError(t, err, "Assets:Approvals is a nmap, therefore requires that number of arguments " + + "should exactly match number of hashers in metadata. Expected: 3, received: 0") + + _, err = CreateStorageKey(m, "Assets", "Approvals", nil) + assert.EqualError(t, err, "Assets:Approvals is a nmap, therefore requires that number of arguments " + + "should exactly match number of hashers in metadata. Expected: 3, received: 0") + + _, err = CreateStorageKey(m, "Assets", "Approvals", nil, []byte{}) + assert.EqualError(t, err, "Assets:Approvals is a nmap, therefore requires that number of arguments " + + "should exactly match number of hashers in metadata. Expected: 3, received: 0") + + _, err = CreateStorageKey(m, "Assets", "Approvals", nil, []byte{0x01}) + assert.EqualError(t, err, "non-nil arguments cannot be preceded by nil arguments") + + _, err = CreateStorageKey(m, "Assets", "Approvals", []byte{0x01}) + assert.EqualError(t, err, "Assets:Approvals is a nmap, therefore requires that number of arguments " + + "should exactly match number of hashers in metadata. Expected: 3, received: 1") + + // Serialize EraIndex and AccountId + var assetId uint32 = 3 + assetIdSerialized := make([]byte, 4) + binary.LittleEndian.PutUint32(assetIdSerialized, assetId) + // Will be used both as owner as well as delegate + accountIdSerialized := MustHexDecodeString(AlicePubKey) + + // Build expected answer + expectedKeyBuilder := strings.Builder{} + hexStr, err := Hex(xxhash.New128([]byte("Assets")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(hexStr) + hexStr, err = Hex(xxhash.New128([]byte("Approvals")).Sum(nil)) + assert.NoError(t, err) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + // Hashing serialized asset id + assetIdHasher, err := hash.NewBlake2b128Concat(nil) + assert.NoError(t, err) + _, err = assetIdHasher.Write(assetIdSerialized) + assert.NoError(t, err) + hexStr, err = Hex(assetIdHasher.Sum(nil)) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + // Hashing serialized account id + accountIdHasher, err := hash.NewBlake2b128Concat(nil) + assert.NoError(t, err) + _, err = accountIdHasher.Write(accountIdSerialized) + assert.NoError(t, err) + hexStr, err = Hex(accountIdHasher.Sum(nil)) + // Writing it multiple times as both owner and delegate + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + expectedKeyBuilder.WriteString(strings.TrimPrefix(hexStr, "0x")) + + + key, err := CreateStorageKey(m, "Assets", "Approvals", assetIdSerialized, accountIdSerialized, + accountIdSerialized) + assert.NoError(t, err) + hex, err := Hex(key) + assert.NoError(t, err) + + assert.Equal(t, expectedKeyBuilder.String(), hex) +} + func TestCreateStorageKeyPlainV13(t *testing.T) { m := ExamplaryMetadataV13 - key, err := CreateStorageKey(m, "Timestamp", "Now", nil) + key, err := CreateStorageKey(m, "Timestamp", "Now") assert.NoError(t, err) hex, err := Hex(key) assert.NoError(t, err) @@ -40,7 +236,7 @@ func TestCreateStorageKeyPlainV13(t *testing.T) { func TestCreateStorageKeyPlainV10(t *testing.T) { m := ExamplaryMetadataV10 - key, err := CreateStorageKey(m, "Timestamp", "Now", nil) + key, err := CreateStorageKey(m, "Timestamp", "Now") assert.NoError(t, err) hex, err := Hex(key) assert.NoError(t, err) @@ -50,7 +246,7 @@ func TestCreateStorageKeyPlainV10(t *testing.T) { func TestCreateStorageKeyPlainV9(t *testing.T) { m := ExamplaryMetadataV9 - key, err := CreateStorageKey(m, "Timestamp", "Now", nil) + key, err := CreateStorageKey(m, "Timestamp", "Now") assert.NoError(t, err) hex, err := Hex(key) assert.NoError(t, err) @@ -60,7 +256,7 @@ func TestCreateStorageKeyPlainV9(t *testing.T) { func TestCreateStorageKeyPlainV4(t *testing.T) { m := ExamplaryMetadataV4 - key, err := CreateStorageKey(m, "Timestamp", "Now", nil) + key, err := CreateStorageKey(m, "Timestamp", "Now") assert.NoError(t, err) hex, err := Hex(key) assert.NoError(t, err)