From b8fec5e4e32f6b56c571a5c641a45155f0021b2b Mon Sep 17 00:00:00 2001 From: Rob Mulholand Date: Mon, 21 Oct 2019 17:08:09 -0500 Subject: [PATCH] (VDB-929) Minimize storage key lookup bespoke code - Extract shared namespace for looking up and hashing keys - Require storage transformers only to implement a loader that associates known keys with metadata - Move key loader/lookup utils to utils directory to avoid multiple "storage" packages in imports --- libraries/shared/factories/storage/README.md | 36 ++++-- .../shared/factories/storage/keys_loader.go | 28 +++++ .../shared/factories/storage/keys_lookup.go | 66 ++++++++++ .../factories/storage/keys_lookup_test.go | 113 ++++++++++++++++++ .../shared/factories/storage/transformer.go | 11 +- .../factories/storage/transformer_test.go | 26 ++-- libraries/shared/mocks/storage_keys_loader.go | 39 ++++++ ...age_mappings.go => storage_keys_lookup.go} | 7 +- .../{mappings.go => utils/keys_loader.go} | 34 +----- .../keys_loader_test.go} | 66 +++++----- libraries/shared/storage/utils/keys_lookup.go | 37 ++++++ .../shared/storage/utils/keys_lookup_test.go | 47 ++++++++ 12 files changed, 412 insertions(+), 98 deletions(-) create mode 100644 libraries/shared/factories/storage/keys_loader.go create mode 100644 libraries/shared/factories/storage/keys_lookup.go create mode 100644 libraries/shared/factories/storage/keys_lookup_test.go create mode 100644 libraries/shared/mocks/storage_keys_loader.go rename libraries/shared/mocks/{storage_mappings.go => storage_keys_lookup.go} (85%) rename libraries/shared/storage/{mappings.go => utils/keys_loader.go} (70%) rename libraries/shared/storage/{mappings_test.go => utils/keys_loader_test.go} (59%) create mode 100644 libraries/shared/storage/utils/keys_lookup.go create mode 100644 libraries/shared/storage/utils/keys_lookup_test.go diff --git a/libraries/shared/factories/storage/README.md b/libraries/shared/factories/storage/README.md index e22eb31b..f79e8987 100644 --- a/libraries/shared/factories/storage/README.md +++ b/libraries/shared/factories/storage/README.md @@ -31,15 +31,15 @@ The storage transformer depends on contract-specific implementations of code cap ```golang func (transformer Transformer) Execute(row shared.StorageDiffRow) error { - metadata, lookupErr := transformer.Mappings.Lookup(row.StorageKey) + metadata, lookupErr := transformer.StorageKeysLookup.Lookup(diff.StorageKey) if lookupErr != nil { return lookupErr } - value, decodeErr := shared.Decode(row, metadata) + value, decodeErr := utils.Decode(diff, metadata) if decodeErr != nil { return decodeErr } - return transformer.Repository.Create(row.BlockHeight, row.BlockHash.Hex(), metadata, value) + return transformer.Repository.Create(diff.BlockHeight, diff.BlockHash.Hex(), metadata, value) } ``` @@ -47,20 +47,36 @@ func (transformer Transformer) Execute(row shared.StorageDiffRow) error { In order to watch an additional smart contract, a developer must create three things: -1. Mappings - specify how to identify keys in the contract's storage trie. +1. StorageKeysLoader - identify keys in the contract's storage trie, providing metadata to describe how associated values should be decoded. 1. Repository - specify how to persist a parsed version of the storage value matching the recognized storage key. 1. Instance - create an instance of the storage transformer that uses your mappings and repository. -### Mappings +### StorageKeysLoader + +A `StorageKeysLoader` is used by the `StorageKeysLookup` object on a storage transformer. ```golang -type Mappings interface { - Lookup(key common.Hash) (shared.StorageValueMetadata, error) +type KeysLoader interface { + LoadMappings() (map[common.Hash]utils.StorageValueMetadata, error) SetDB(db *postgres.DB) } ``` -A contract-specific implementation of the mappings interface enables the storage transformer to fetch metadata associated with a storage key. +When a key is not found, the lookup object refreshes its known keys by calling the loader. + +```golang +func (lookup *keysLookup) refreshMappings() error { + var err error + lookup.mappings, err = lookup.loader.LoadMappings() + if err != nil { + return err + } + lookup.mappings = utils.AddHashedKeys(lookup.mappings) + return nil +} +``` + +A contract-specific implementation of the loader enables the storage transformer to fetch metadata associated with a storage key. Storage metadata contains: the name of the variable matching the storage key, a raw version of any keys associated with the variable (if the variable is a mapping), and the variable's type. @@ -72,7 +88,7 @@ type StorageValueMetadata struct { } ``` -Keys are only relevant if the variable is a mapping. For example, in the following Solidity code: +The `Keys` field on the metadata is only relevant if the variable is a mapping. For example, in the following Solidity code: ```solidity pragma solidity ^0.4.0; @@ -85,7 +101,7 @@ contract Contract { The metadata for variable `x` would not have any associated keys, but the metadata for a storage key associated with `y` would include the address used to specify that key's index in the mapping. -The `SetDB` function is required for the mappings to connect to the database. +The `SetDB` function is required for the storage key loader to connect to the database. A database connection may be desired when keys in a mapping variable need to be read from log events (e.g. to lookup what addresses may exist in `y`, above). ### Repository diff --git a/libraries/shared/factories/storage/keys_loader.go b/libraries/shared/factories/storage/keys_loader.go new file mode 100644 index 00000000..1b6efdbb --- /dev/null +++ b/libraries/shared/factories/storage/keys_loader.go @@ -0,0 +1,28 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package storage + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type KeysLoader interface { + LoadMappings() (map[common.Hash]utils.StorageValueMetadata, error) + SetDB(db *postgres.DB) +} diff --git a/libraries/shared/factories/storage/keys_lookup.go b/libraries/shared/factories/storage/keys_lookup.go new file mode 100644 index 00000000..1c641c20 --- /dev/null +++ b/libraries/shared/factories/storage/keys_lookup.go @@ -0,0 +1,66 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package storage + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type KeysLookup interface { + Lookup(key common.Hash) (utils.StorageValueMetadata, error) + SetDB(db *postgres.DB) +} + +type keysLookup struct { + loader KeysLoader + mappings map[common.Hash]utils.StorageValueMetadata +} + +func NewKeysLookup(loader KeysLoader) KeysLookup { + return &keysLookup{loader: loader, mappings: make(map[common.Hash]utils.StorageValueMetadata)} +} + +func (lookup *keysLookup) Lookup(key common.Hash) (utils.StorageValueMetadata, error) { + metadata, ok := lookup.mappings[key] + if !ok { + refreshErr := lookup.refreshMappings() + if refreshErr != nil { + return metadata, refreshErr + } + metadata, ok = lookup.mappings[key] + if !ok { + return metadata, utils.ErrStorageKeyNotFound{Key: key.Hex()} + } + } + return metadata, nil +} + +func (lookup *keysLookup) refreshMappings() error { + var err error + lookup.mappings, err = lookup.loader.LoadMappings() + if err != nil { + return err + } + lookup.mappings = utils.AddHashedKeys(lookup.mappings) + return nil +} + +func (lookup *keysLookup) SetDB(db *postgres.DB) { + lookup.loader.SetDB(db) +} diff --git a/libraries/shared/factories/storage/keys_lookup_test.go b/libraries/shared/factories/storage/keys_lookup_test.go new file mode 100644 index 00000000..6a77b164 --- /dev/null +++ b/libraries/shared/factories/storage/keys_lookup_test.go @@ -0,0 +1,113 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package storage_test + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/libraries/shared/factories/storage" + "github.com/vulcanize/vulcanizedb/libraries/shared/mocks" + "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" + "github.com/vulcanize/vulcanizedb/pkg/fakes" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Storage keys lookup", func() { + var ( + fakeMetadata = utils.GetStorageValueMetadata("name", map[utils.Key]string{}, utils.Uint256) + lookup storage.KeysLookup + loader *mocks.MockStorageKeysLoader + ) + + BeforeEach(func() { + loader = &mocks.MockStorageKeysLoader{} + lookup = storage.NewKeysLookup(loader) + }) + + Describe("Lookup", func() { + Describe("when key not found", func() { + It("refreshes keys", func() { + loader.StorageKeyMappings = map[common.Hash]utils.StorageValueMetadata{fakes.FakeHash: fakeMetadata} + _, err := lookup.Lookup(fakes.FakeHash) + + Expect(err).NotTo(HaveOccurred()) + Expect(loader.LoadMappingsCallCount).To(Equal(1)) + }) + + It("returns error if refreshing keys fails", func() { + loader.LoadMappingsError = fakes.FakeError + + _, err := lookup.Lookup(fakes.FakeHash) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + }) + + Describe("when key found", func() { + BeforeEach(func() { + loader.StorageKeyMappings = map[common.Hash]utils.StorageValueMetadata{fakes.FakeHash: fakeMetadata} + _, err := lookup.Lookup(fakes.FakeHash) + Expect(err).NotTo(HaveOccurred()) + Expect(loader.LoadMappingsCallCount).To(Equal(1)) + }) + + It("does not refresh keys", func() { + _, err := lookup.Lookup(fakes.FakeHash) + + Expect(err).NotTo(HaveOccurred()) + Expect(loader.LoadMappingsCallCount).To(Equal(1)) + }) + }) + + It("returns metadata for loaded static key", func() { + loader.StorageKeyMappings = map[common.Hash]utils.StorageValueMetadata{fakes.FakeHash: fakeMetadata} + + metadata, err := lookup.Lookup(fakes.FakeHash) + + Expect(err).NotTo(HaveOccurred()) + Expect(metadata).To(Equal(fakeMetadata)) + }) + + It("returns metadata for hashed version of key (accommodates keys emitted from Geth)", func() { + loader.StorageKeyMappings = map[common.Hash]utils.StorageValueMetadata{fakes.FakeHash: fakeMetadata} + + hashedKey := common.BytesToHash(crypto.Keccak256(fakes.FakeHash.Bytes())) + metadata, err := lookup.Lookup(hashedKey) + + Expect(err).NotTo(HaveOccurred()) + Expect(metadata).To(Equal(fakeMetadata)) + }) + + It("returns key not found error if key not found", func() { + _, err := lookup.Lookup(fakes.FakeHash) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(utils.ErrStorageKeyNotFound{Key: fakes.FakeHash.Hex()})) + }) + }) + + Describe("SetDB", func() { + It("sets the db on the loader", func() { + lookup.SetDB(test_config.NewTestDB(test_config.NewTestNode())) + + Expect(loader.SetDBCalled).To(BeTrue()) + }) + }) +}) diff --git a/libraries/shared/factories/storage/transformer.go b/libraries/shared/factories/storage/transformer.go index b38e5760..25e39811 100644 --- a/libraries/shared/factories/storage/transformer.go +++ b/libraries/shared/factories/storage/transformer.go @@ -18,20 +18,19 @@ package storage import ( "github.com/ethereum/go-ethereum/common" - "github.com/vulcanize/vulcanizedb/libraries/shared/storage" "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" "github.com/vulcanize/vulcanizedb/libraries/shared/transformer" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" ) type Transformer struct { - HashedAddress common.Hash - Mappings storage.Mappings - Repository Repository + HashedAddress common.Hash + StorageKeysLookup KeysLookup + Repository Repository } func (transformer Transformer) NewTransformer(db *postgres.DB) transformer.StorageTransformer { - transformer.Mappings.SetDB(db) + transformer.StorageKeysLookup.SetDB(db) transformer.Repository.SetDB(db) return transformer } @@ -41,7 +40,7 @@ func (transformer Transformer) KeccakContractAddress() common.Hash { } func (transformer Transformer) Execute(diff utils.StorageDiff) error { - metadata, lookupErr := transformer.Mappings.Lookup(diff.StorageKey) + metadata, lookupErr := transformer.StorageKeysLookup.Lookup(diff.StorageKey) if lookupErr != nil { return lookupErr } diff --git a/libraries/shared/factories/storage/transformer_test.go b/libraries/shared/factories/storage/transformer_test.go index 8fe87e52..fddb082f 100644 --- a/libraries/shared/factories/storage/transformer_test.go +++ b/libraries/shared/factories/storage/transformer_test.go @@ -28,18 +28,18 @@ import ( var _ = Describe("Storage transformer", func() { var ( - mappings *mocks.MockMappings - repository *mocks.MockStorageRepository - t storage.Transformer + storageKeysLookup *mocks.MockStorageKeysLookup + repository *mocks.MockStorageRepository + t storage.Transformer ) BeforeEach(func() { - mappings = &mocks.MockMappings{} + storageKeysLookup = &mocks.MockStorageKeysLookup{} repository = &mocks.MockStorageRepository{} t = storage.Transformer{ - HashedAddress: common.Hash{}, - Mappings: mappings, - Repository: repository, + HashedAddress: common.Hash{}, + StorageKeysLookup: storageKeysLookup, + Repository: repository, } }) @@ -53,11 +53,11 @@ var _ = Describe("Storage transformer", func() { It("looks up metadata for storage key", func() { t.Execute(utils.StorageDiff{}) - Expect(mappings.LookupCalled).To(BeTrue()) + Expect(storageKeysLookup.LookupCalled).To(BeTrue()) }) It("returns error if lookup fails", func() { - mappings.LookupErr = fakes.FakeError + storageKeysLookup.LookupErr = fakes.FakeError err := t.Execute(utils.StorageDiff{}) @@ -67,7 +67,7 @@ var _ = Describe("Storage transformer", func() { It("creates storage row with decoded data", func() { fakeMetadata := utils.StorageValueMetadata{Type: utils.Address} - mappings.Metadata = fakeMetadata + storageKeysLookup.Metadata = fakeMetadata rawValue := common.HexToAddress("0x12345") fakeBlockNumber := 123 fakeBlockHash := "0x67890" @@ -91,7 +91,7 @@ var _ = Describe("Storage transformer", func() { It("returns error if creating row fails", func() { rawValue := common.HexToAddress("0x12345") fakeMetadata := utils.StorageValueMetadata{Type: utils.Address} - mappings.Metadata = fakeMetadata + storageKeysLookup.Metadata = fakeMetadata repository.CreateErr = fakes.FakeError err := t.Execute(utils.StorageDiff{StorageValue: rawValue.Hash()}) @@ -118,7 +118,7 @@ var _ = Describe("Storage transformer", func() { } It("passes the decoded data items to the repository", func() { - mappings.Metadata = fakeMetadata + storageKeysLookup.Metadata = fakeMetadata fakeRow := utils.StorageDiff{ HashedAddress: common.Hash{}, BlockHash: common.HexToHash(fakeBlockHash), @@ -140,7 +140,7 @@ var _ = Describe("Storage transformer", func() { }) It("returns error if creating a row fails", func() { - mappings.Metadata = fakeMetadata + storageKeysLookup.Metadata = fakeMetadata repository.CreateErr = fakes.FakeError err := t.Execute(utils.StorageDiff{StorageValue: rawValue.Hash()}) diff --git a/libraries/shared/mocks/storage_keys_loader.go b/libraries/shared/mocks/storage_keys_loader.go new file mode 100644 index 00000000..4b2c0812 --- /dev/null +++ b/libraries/shared/mocks/storage_keys_loader.go @@ -0,0 +1,39 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package mocks + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type MockStorageKeysLoader struct { + LoadMappingsCallCount int + LoadMappingsError error + SetDBCalled bool + StorageKeyMappings map[common.Hash]utils.StorageValueMetadata +} + +func (loader *MockStorageKeysLoader) LoadMappings() (map[common.Hash]utils.StorageValueMetadata, error) { + loader.LoadMappingsCallCount++ + return loader.StorageKeyMappings, loader.LoadMappingsError +} + +func (loader *MockStorageKeysLoader) SetDB(db *postgres.DB) { + loader.SetDBCalled = true +} diff --git a/libraries/shared/mocks/storage_mappings.go b/libraries/shared/mocks/storage_keys_lookup.go similarity index 85% rename from libraries/shared/mocks/storage_mappings.go rename to libraries/shared/mocks/storage_keys_lookup.go index d2f681bb..033e68c8 100644 --- a/libraries/shared/mocks/storage_mappings.go +++ b/libraries/shared/mocks/storage_keys_lookup.go @@ -18,22 +18,21 @@ package mocks import ( "github.com/ethereum/go-ethereum/common" - "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" ) -type MockMappings struct { +type MockStorageKeysLookup struct { Metadata utils.StorageValueMetadata LookupCalled bool LookupErr error } -func (mappings *MockMappings) Lookup(key common.Hash) (utils.StorageValueMetadata, error) { +func (mappings *MockStorageKeysLookup) Lookup(key common.Hash) (utils.StorageValueMetadata, error) { mappings.LookupCalled = true return mappings.Metadata, mappings.LookupErr } -func (*MockMappings) SetDB(db *postgres.DB) { +func (*MockStorageKeysLookup) SetDB(db *postgres.DB) { panic("implement me") } diff --git a/libraries/shared/storage/mappings.go b/libraries/shared/storage/utils/keys_loader.go similarity index 70% rename from libraries/shared/storage/mappings.go rename to libraries/shared/storage/utils/keys_loader.go index f8b089ae..0142b564 100644 --- a/libraries/shared/storage/mappings.go +++ b/libraries/shared/storage/utils/keys_loader.go @@ -14,23 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package storage +package utils import ( - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - - "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" - "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "math/big" ) -type Mappings interface { - Lookup(key common.Hash) (utils.StorageValueMetadata, error) - SetDB(db *postgres.DB) -} - const ( IndexZero = "0000000000000000000000000000000000000000000000000000000000000000" IndexOne = "0000000000000000000000000000000000000000000000000000000000000001" @@ -46,32 +37,17 @@ const ( IndexEleven = "000000000000000000000000000000000000000000000000000000000000000b" ) -func AddHashedKeys(currentMappings map[common.Hash]utils.StorageValueMetadata) map[common.Hash]utils.StorageValueMetadata { - copyOfCurrentMappings := make(map[common.Hash]utils.StorageValueMetadata) - for k, v := range currentMappings { - copyOfCurrentMappings[k] = v - } - for k, v := range copyOfCurrentMappings { - currentMappings[hashKey(k)] = v - } - return currentMappings -} - -func hashKey(key common.Hash) common.Hash { - return crypto.Keccak256Hash(key.Bytes()) -} - -func GetMapping(indexOnContract, key string) common.Hash { +func GetStorageKeyForMapping(indexOnContract, key string) common.Hash { keyBytes := common.FromHex(key + indexOnContract) return crypto.Keccak256Hash(keyBytes) } -func GetNestedMapping(indexOnContract, primaryKey, secondaryKey string) common.Hash { +func GetStorageKeyForNestedMapping(indexOnContract, primaryKey, secondaryKey string) common.Hash { primaryMappingIndex := crypto.Keccak256(common.FromHex(primaryKey + indexOnContract)) return crypto.Keccak256Hash(common.FromHex(secondaryKey), primaryMappingIndex) } -func GetIncrementedKey(original common.Hash, incrementBy int64) common.Hash { +func GetIncrementedStorageKey(original common.Hash, incrementBy int64) common.Hash { originalMappingAsInt := original.Big() incremented := big.NewInt(0).Add(originalMappingAsInt, big.NewInt(incrementBy)) return common.BytesToHash(incremented.Bytes()) diff --git a/libraries/shared/storage/mappings_test.go b/libraries/shared/storage/utils/keys_loader_test.go similarity index 59% rename from libraries/shared/storage/mappings_test.go rename to libraries/shared/storage/utils/keys_loader_test.go index bc077f1f..51ad58fd 100644 --- a/libraries/shared/storage/mappings_test.go +++ b/libraries/shared/storage/utils/keys_loader_test.go @@ -1,78 +1,72 @@ -package storage_test +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package utils_test import ( "github.com/ethereum/go-ethereum/common" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/libraries/shared/storage" "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" ) -var _ = Describe("Mappings", func() { - Describe("AddHashedKeys", func() { - It("returns a copy of the map with an additional slot for the hashed version of every key", func() { - fakeMap := map[common.Hash]utils.StorageValueMetadata{} - fakeStorageKey := common.HexToHash("72c72de6b203d67cb6cd54fc93300109fcc6fd6eac88e390271a3d548794d800") - var fakeMappingKey utils.Key = "fakeKey" - fakeMetadata := utils.StorageValueMetadata{ - Name: "fakeName", - Keys: map[utils.Key]string{fakeMappingKey: "fakeValue"}, - Type: utils.Uint48, - } - fakeMap[fakeStorageKey] = fakeMetadata - - result := storage.AddHashedKeys(fakeMap) - - Expect(len(result)).To(Equal(2)) - expectedHashedStorageKey := common.HexToHash("2165edb4e1c37b99b60fa510d84f939dd35d5cd1d1c8f299d6456ea09df65a76") - Expect(fakeMap[fakeStorageKey]).To(Equal(fakeMetadata)) - Expect(fakeMap[expectedHashedStorageKey]).To(Equal(fakeMetadata)) - }) - }) - - Describe("GetMapping", func() { +var _ = Describe("Storage keys loader utils", func() { + Describe("GetStorageKeyForMapping", func() { It("returns the storage key for a mapping when passed the mapping's index on the contract and the desired value's key", func() { // ex. solidity: // mapping (bytes32 => uint) public amounts // to access amounts, pass in the index of the mapping on the contract + the bytes32 key for the uint val being looked up - indexOfMappingOnContract := storage.IndexZero + indexOfMappingOnContract := utils.IndexZero keyForDesiredValueInMapping := "1234567890abcdef" - storageKey := storage.GetMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) + storageKey := utils.GetStorageKeyForMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) expectedStorageKey := common.HexToHash("0xee0c1b59a3856bafbfb8730e7694c4badc271eb5f01ce4a8d7a53d8a6499676f") Expect(storageKey).To(Equal(expectedStorageKey)) }) It("returns same result if value includes hex prefix", func() { - indexOfMappingOnContract := storage.IndexZero + indexOfMappingOnContract := utils.IndexZero keyForDesiredValueInMapping := "0x1234567890abcdef" - storageKey := storage.GetMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) + storageKey := utils.GetStorageKeyForMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) expectedStorageKey := common.HexToHash("0xee0c1b59a3856bafbfb8730e7694c4badc271eb5f01ce4a8d7a53d8a6499676f") Expect(storageKey).To(Equal(expectedStorageKey)) }) }) - Describe("GetNestedMapping", func() { + Describe("GetStorageKeyForNestedMapping", func() { It("returns the storage key for a nested mapping when passed the mapping's index on the contract and the desired value's keys", func() { // ex. solidity: // mapping (bytes32 => uint) public amounts // mapping (address => mapping (uint => bytes32)) public addressNames // to access addressNames, pass in the index of the mapping on the contract + the address and uint keys for the bytes32 val being looked up - indexOfMappingOnContract := storage.IndexOne + indexOfMappingOnContract := utils.IndexOne keyForOuterMapping := "1234567890abcdef" keyForInnerMapping := "123" - storageKey := storage.GetNestedMapping(indexOfMappingOnContract, keyForOuterMapping, keyForInnerMapping) + storageKey := utils.GetStorageKeyForNestedMapping(indexOfMappingOnContract, keyForOuterMapping, keyForInnerMapping) expectedStorageKey := common.HexToHash("0x82113529f6cd61061d1a6f0de53f2bdd067a1addd3d2b46be50a99abfcdb1661") Expect(storageKey).To(Equal(expectedStorageKey)) }) }) - Describe("GetIncrementedKey", func() { + Describe("GetIncrementedStorageKey", func() { It("returns the storage key for later values sharing an index on the contract with other earlier values", func() { // ex. solidity: // mapping (bytes32 => uint) public amounts @@ -84,11 +78,11 @@ var _ = Describe("Mappings", func() { // mapping (bytes32 => Data) public itemData; // to access quality from itemData, pass in the storage key for the zero-indexed value (quantity) + the number of increments required. // (For "quality", we must increment the storage key for the corresponding "quantity" by 1). - indexOfMappingOnContract := storage.IndexTwo + indexOfMappingOnContract := utils.IndexTwo keyForDesiredValueInMapping := "1234567890abcdef" - storageKeyForFirstPropertyOnStruct := storage.GetMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) + storageKeyForFirstPropertyOnStruct := utils.GetStorageKeyForMapping(indexOfMappingOnContract, keyForDesiredValueInMapping) - storageKey := storage.GetIncrementedKey(storageKeyForFirstPropertyOnStruct, 1) + storageKey := utils.GetIncrementedStorageKey(storageKeyForFirstPropertyOnStruct, 1) expectedStorageKey := common.HexToHash("0x69b38749f0a8ed5d505c8474f7fb62c7828aad8a7627f1c67e07af1d2368cad4") Expect(storageKey).To(Equal(expectedStorageKey)) diff --git a/libraries/shared/storage/utils/keys_lookup.go b/libraries/shared/storage/utils/keys_lookup.go new file mode 100644 index 00000000..fd2c1ee8 --- /dev/null +++ b/libraries/shared/storage/utils/keys_lookup.go @@ -0,0 +1,37 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package utils + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func AddHashedKeys(currentMappings map[common.Hash]StorageValueMetadata) map[common.Hash]StorageValueMetadata { + copyOfCurrentMappings := make(map[common.Hash]StorageValueMetadata) + for k, v := range currentMappings { + copyOfCurrentMappings[k] = v + } + for k, v := range copyOfCurrentMappings { + currentMappings[hashKey(k)] = v + } + return currentMappings +} + +func hashKey(key common.Hash) common.Hash { + return crypto.Keccak256Hash(key.Bytes()) +} diff --git a/libraries/shared/storage/utils/keys_lookup_test.go b/libraries/shared/storage/utils/keys_lookup_test.go new file mode 100644 index 00000000..22ca16df --- /dev/null +++ b/libraries/shared/storage/utils/keys_lookup_test.go @@ -0,0 +1,47 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program 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. + +// This program 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 this program. If not, see . + +package utils_test + +import ( + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils" +) + +var _ = Describe("Storage keys lookup utils", func() { + Describe("AddHashedKeys", func() { + It("returns a copy of the map with an additional slot for the hashed version of every key", func() { + fakeMap := map[common.Hash]utils.StorageValueMetadata{} + fakeStorageKey := common.HexToHash("72c72de6b203d67cb6cd54fc93300109fcc6fd6eac88e390271a3d548794d800") + var fakeMappingKey utils.Key = "fakeKey" + fakeMetadata := utils.StorageValueMetadata{ + Name: "fakeName", + Keys: map[utils.Key]string{fakeMappingKey: "fakeValue"}, + Type: utils.Uint48, + } + fakeMap[fakeStorageKey] = fakeMetadata + + result := utils.AddHashedKeys(fakeMap) + + Expect(len(result)).To(Equal(2)) + expectedHashedStorageKey := common.HexToHash("2165edb4e1c37b99b60fa510d84f939dd35d5cd1d1c8f299d6456ea09df65a76") + Expect(fakeMap[fakeStorageKey]).To(Equal(fakeMetadata)) + Expect(fakeMap[expectedHashedStorageKey]).To(Equal(fakeMetadata)) + }) + }) +})