feat(simulation): Implement store decoder implementation from collections schema (#16074)

Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
testinginprod 2023-05-11 11:13:24 +02:00 committed by GitHub
parent 908677e648
commit 69642f6176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 306 additions and 44 deletions

View File

@ -66,7 +66,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (modulemanager) [#15829](https://github.com/cosmos/cosmos-sdk/pull/15829) add new endblocker interface to handle valset updates
* (core) [#14860](https://github.com/cosmos/cosmos-sdk/pull/14860) Add `Precommit` and `PrepareCheckState` AppModule callbacks.
* (tx) [#15992](https://github.com/cosmos/cosmos-sdk/pull/15992) Add `WithExtensionOptions` in tx Factory to allow `SetExtensionOptions` with given extension options.
* (types/simulation) [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) Add generic SimulationStoreDecoder for modules using collections.
### Improvements
* (client) [#16075](https://github.com/cosmos/cosmos-sdk/pull/16075) Partly revert [#15953](https://github.com/cosmos/cosmos-sdk/issues/15953) and `factory.Prepare` does nothing in offline mode.

View File

@ -31,6 +31,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
### Features
* [#16074](https://github.com/cosmos/cosmos-sdk/pull/16074) makes the generic Collection interface public, still highly unstable.
## [v0.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.1.0)
Collections `v0.1.0` is released! Check out the [docs](https://docs.cosmos.network/main/packages/collections) to know how to use the APIs.

View File

@ -74,6 +74,56 @@ type ValueCodec[T any] interface {
ValueType() string
}
// NewUntypedValueCodec returns an UntypedValueCodec for the provided ValueCodec.
func NewUntypedValueCodec[V any](v ValueCodec[V]) UntypedValueCodec {
typeName := fmt.Sprintf("%T", *new(V))
checkType := func(value interface{}) (v V, err error) {
concrete, ok := value.(V)
if !ok {
return v, fmt.Errorf("%w: expected value of type %s, got %T", ErrEncoding, typeName, value)
}
return concrete, nil
}
return UntypedValueCodec{
Decode: func(b []byte) (interface{}, error) { return v.Decode(b) },
Encode: func(value interface{}) ([]byte, error) {
concrete, err := checkType(value)
if err != nil {
return nil, err
}
return v.Encode(concrete)
},
DecodeJSON: func(b []byte) (interface{}, error) {
return v.DecodeJSON(b)
},
EncodeJSON: func(value interface{}) ([]byte, error) {
concrete, err := checkType(value)
if err != nil {
return nil, err
}
return v.EncodeJSON(concrete)
},
Stringify: func(value interface{}) (string, error) {
concrete, err := checkType(value)
if err != nil {
return "", err
}
return v.Stringify(concrete), nil
},
ValueType: func() string { return v.ValueType() },
}
}
// UntypedValueCodec wraps a ValueCodec to expose an untyped API for encoding and decoding values.
type UntypedValueCodec struct {
Decode func(b []byte) (interface{}, error)
Encode func(value interface{}) ([]byte, error)
DecodeJSON func(b []byte) (interface{}, error)
EncodeJSON func(value interface{}) ([]byte, error)
Stringify func(value interface{}) (string, error)
ValueType func() string
}
// KeyToValueCodec converts a KeyCodec into a ValueCodec.
func KeyToValueCodec[K any](keyCodec KeyCodec[K]) ValueCodec[K] { return keyToValueCodec[K]{keyCodec} }

View File

@ -0,0 +1,39 @@
package codec
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUntypedValueCodec(t *testing.T) {
vc := NewUntypedValueCodec(KeyToValueCodec(NewStringKeyCodec[string]()))
t.Run("encode/decode", func(t *testing.T) {
_, err := vc.Encode(0)
require.ErrorIs(t, err, ErrEncoding)
b, err := vc.Encode("hello")
require.NoError(t, err)
value, err := vc.Decode(b)
require.NoError(t, err)
require.Equal(t, "hello", value)
})
t.Run("json encode/decode", func(t *testing.T) {
_, err := vc.EncodeJSON(0)
require.ErrorIs(t, err, ErrEncoding)
b, err := vc.EncodeJSON("hello")
require.NoError(t, err)
value, err := vc.DecodeJSON(b)
require.NoError(t, err)
require.Equal(t, "hello", value)
})
t.Run("stringify", func(t *testing.T) {
_, err := vc.Stringify(0)
require.ErrorIs(t, err, ErrEncoding)
s, err := vc.Stringify("hello")
require.NoError(t, err)
require.Equal(t, "hello", s)
})
}

View File

@ -1,7 +1,9 @@
package collections
import (
"context"
"errors"
io "io"
"math"
"cosmossdk.io/collections/codec"
@ -72,16 +74,20 @@ var (
BytesValue = codec.KeyToValueCodec(BytesKey)
)
// collection is the interface that all collections support. It will eventually
// Collection is the interface that all collections implement. It will eventually
// include methods for importing/exporting genesis data and schema
// reflection for clients.
type collection interface {
// getName is the unique name of the collection within a schema. It must
// NOTE: Unstable.
type Collection interface {
// GetName is the unique name of the collection within a schema. It must
// match format specified by NameRegex.
getName() string
GetName() string
// getPrefix is the unique prefix of the collection within a schema.
getPrefix() []byte
// GetPrefix is the unique prefix of the collection within a schema.
GetPrefix() []byte
// ValueCodec returns the codec used to encode/decode values of the collection.
ValueCodec() codec.UntypedValueCodec
genesisHandler
}
@ -122,3 +128,32 @@ func NewPrefix[T interface{ int | string | []byte }](identifier T) Prefix {
}
return prefix
}
var _ Collection = (*collectionImpl[string, string])(nil)
// collectionImpl wraps a Map and implements Collection. This properly splits
// the generic and untyped Collection interface from the typed Map, which every
// collection builds on.
type collectionImpl[K, V any] struct {
m Map[K, V]
}
func (c collectionImpl[K, V]) ValueCodec() codec.UntypedValueCodec {
return codec.NewUntypedValueCodec(c.m.vc)
}
func (c collectionImpl[K, V]) GetName() string { return c.m.name }
func (c collectionImpl[K, V]) GetPrefix() []byte { return NewPrefix(c.m.prefix) }
func (c collectionImpl[K, V]) validateGenesis(r io.Reader) error { return c.m.validateGenesis(r) }
func (c collectionImpl[K, V]) importGenesis(ctx context.Context, r io.Reader) error {
return c.m.importGenesis(ctx, r)
}
func (c collectionImpl[K, V]) exportGenesis(ctx context.Context, w io.Writer) error {
return c.m.exportGenesis(ctx, w)
}
func (c collectionImpl[K, V]) defaultGenesis(w io.Writer) error { return c.m.defaultGenesis(w) }

View File

@ -302,7 +302,7 @@ type KeyValue[K, V any] struct {
// encodeRangeBound encodes a range bound, modifying the key bytes to adhere to bound semantics.
func encodeRangeBound[T any](prefix []byte, keyCodec codec.KeyCodec[T], bound *RangeKey[T]) ([]byte, error) {
key, err := encodeKeyWithPrefix(prefix, keyCodec, bound.key)
key, err := EncodeKeyWithPrefix(prefix, keyCodec, bound.key)
if err != nil {
return nil, err
}

View File

@ -39,22 +39,22 @@ func NewMap[K, V any](
prefix: prefix.Bytes(),
name: name,
}
schemaBuilder.addCollection(m)
schemaBuilder.addCollection(collectionImpl[K, V]{m})
return m
}
func (m Map[K, V]) getName() string {
func (m Map[K, V]) GetName() string {
return m.name
}
func (m Map[K, V]) getPrefix() []byte {
func (m Map[K, V]) GetPrefix() []byte {
return m.prefix
}
// Set maps the provided value to the provided key in the store.
// Errors with ErrEncoding if key or value encoding fails.
func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
@ -73,7 +73,7 @@ func (m Map[K, V]) Set(ctx context.Context, key K, value V) error {
// errors with ErrNotFound if the key does not exist, or
// with ErrEncoding if the key or value decoding fails.
func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return v, err
}
@ -97,7 +97,7 @@ func (m Map[K, V]) Get(ctx context.Context, key K) (v V, err error) {
// Has reports whether the key is present in storage or not.
// Errors with ErrEncoding if key encoding fails.
func (m Map[K, V]) Has(ctx context.Context, key K) (bool, error) {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return false, err
}
@ -109,7 +109,7 @@ func (m Map[K, V]) Has(ctx context.Context, key K) (bool, error) {
// Errors with ErrEncoding if key encoding fails.
// If the key does not exist then this is a no-op.
func (m Map[K, V]) Remove(ctx context.Context, key K) error {
bytesKey, err := encodeKeyWithPrefix(m.prefix, m.kc, key)
bytesKey, err := EncodeKeyWithPrefix(m.prefix, m.kc, key)
if err != nil {
return err
}
@ -195,7 +195,9 @@ func (m Map[K, V]) KeyCodec() codec.KeyCodec[K] { return m.kc }
// ValueCodec returns the Map's ValueCodec.
func (m Map[K, V]) ValueCodec() codec.ValueCodec[V] { return m.vc }
func encodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]byte, error) {
// EncodeKeyWithPrefix returns how the collection would store the key in storage given
// prefix, key codec and the concrete key.
func EncodeKeyWithPrefix[K any](prefix []byte, kc codec.KeyCodec[K], key K) ([]byte, error) {
prefixLen := len(prefix)
// preallocate buffer
keyBytes := make([]byte, prefixLen+kc.Size(key))

View File

@ -50,7 +50,7 @@ func TestMap_IterateRaw(t *testing.T) {
require.NoError(t, m.Set(ctx, 2, 2))
// test non nil end in ascending order
twoBigEndian, err := encodeKeyWithPrefix(nil, Uint64Key, 2)
twoBigEndian, err := EncodeKeyWithPrefix(nil, Uint64Key, 2)
require.NoError(t, err)
iter, err := m.IterateRaw(ctx, nil, twoBigEndian, OrderAscending)
require.NoError(t, err)
@ -76,7 +76,7 @@ func Test_encodeKey(t *testing.T) {
number := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
expectedKey := append([]byte(prefix), number...)
gotKey, err := encodeKeyWithPrefix(NewPrefix(prefix).Bytes(), Uint64Key, 0)
gotKey, err := EncodeKeyWithPrefix(NewPrefix(prefix).Bytes(), Uint64Key, 0)
require.NoError(t, err)
require.Equal(t, expectedKey, gotKey)
}

View File

@ -25,8 +25,8 @@ func NewSchemaBuilderFromAccessor(accessorFunc func(ctx context.Context) store.K
return &SchemaBuilder{
schema: &Schema{
storeAccessor: accessorFunc,
collectionsByName: map[string]collection{},
collectionsByPrefix: map[string]collection{},
collectionsByName: map[string]Collection{},
collectionsByPrefix: map[string]Collection{},
},
}
}
@ -84,9 +84,9 @@ func (s *SchemaBuilder) Build() (Schema, error) {
return schema, nil
}
func (s *SchemaBuilder) addCollection(collection collection) {
prefix := collection.getPrefix()
name := collection.getName()
func (s *SchemaBuilder) addCollection(collection Collection) {
prefix := collection.GetPrefix()
name := collection.GetName()
if _, ok := s.schema.collectionsByPrefix[string(prefix)]; ok {
s.appendError(fmt.Errorf("prefix %v already taken within schema", prefix))
@ -128,8 +128,8 @@ var nameRegex = regexp.MustCompile("^" + NameRegex + "$")
type Schema struct {
storeAccessor func(context.Context) store.KVStore
collectionsOrdered []string
collectionsByPrefix map[string]collection
collectionsByName map[string]collection
collectionsByPrefix map[string]Collection
collectionsByName map[string]Collection
}
// NewSchema creates a new schema for the provided KVStoreService.
@ -157,8 +157,8 @@ func NewMemoryStoreSchema(service store.MemoryStoreService) Schema {
func NewSchemaFromAccessor(accessor func(context.Context) store.KVStore) Schema {
return Schema{
storeAccessor: accessor,
collectionsByName: map[string]collection{},
collectionsByPrefix: map[string]collection{},
collectionsByName: map[string]Collection{},
collectionsByPrefix: map[string]Collection{},
}
}
@ -279,10 +279,18 @@ func (s Schema) exportGenesis(ctx context.Context, target appmodule.GenesisTarge
return coll.exportGenesis(ctx, wc)
}
func (s Schema) getCollection(name string) (collection, error) {
func (s Schema) getCollection(name string) (Collection, error) {
coll, ok := s.collectionsByName[name]
if !ok {
return nil, fmt.Errorf("unknown collection: %s", name)
}
return coll, nil
}
func (s Schema) ListCollections() []Collection {
colls := make([]Collection, len(s.collectionsOrdered))
for i, name := range s.collectionsOrdered {
colls[i] = s.collectionsByName[name]
}
return colls
}

2
go.mod
View File

@ -162,6 +162,8 @@ require (
// Below are the long-lived replace of the Cosmos SDK
replace (
// TODO: remove me after collections 0.2. is released.
cosmossdk.io/collections => ./collections
cosmossdk.io/core => ./core
cosmossdk.io/store => ./store
// TODO: remove after 0.7.0 release

2
go.sum
View File

@ -37,8 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=

View File

@ -209,6 +209,8 @@ replace (
// Below are the long-lived replace of the SimApp
replace (
// TODO: remove me after collections 0.2. is released.
cosmossdk.io/collections => ../collections
cosmossdk.io/core => ../core
// TODO: remove after 0.7.0 release
cosmossdk.io/x/tx => ../x/tx

View File

@ -188,8 +188,6 @@ cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xX
cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=

View File

@ -202,6 +202,8 @@ replace (
// Below are the long-lived replace for tests.
replace (
// TODO: remove me after collections v0.2.0 is released
cosmossdk.io/collections => ../collections
cosmossdk.io/core => ../core
// We always want to test against the latest version of the simapp.
cosmossdk.io/simapp => ../simapp

View File

@ -190,8 +190,6 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba h1:LuPHCncU2KLMNPItFECs709uo46I9wSu2fAWYVCx+/U=
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba/go.mod h1:SXdwqO7cN5htalh/lhXWP8V4zKtBrhhcSTU+ytuEtmM=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=

View File

@ -155,6 +155,8 @@ require (
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace cosmossdk.io/collections => ../../collections
// Fix upstream GHSA-h395-qcrw-5vmq vulnerability.
// TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409
// TODO investigate if we can outright delete this dependency, otherwise go install won't work :(

View File

@ -37,8 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s=
cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=

View File

@ -120,3 +120,5 @@ require (
pgregory.net/rapid v0.5.7 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace cosmossdk.io/collections => ../../collections // TODO: remove me after collections v0.2.0 is released

View File

@ -39,8 +39,6 @@ cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/client/v2 v2.0.0-20230426154441-2037a26d1235 h1:6aGhtjUgmacucrKMC9ZdF9G96YoxZqkTC2ZyxaAg1GE=
cosmossdk.io/client/v2 v2.0.0-20230426154441-2037a26d1235/go.mod h1:ydI6QS3A+K2px6O8QpM0JtNaVV6lLeCJ5LVwtQXIMAg=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 h1:l1scDTT2VX18ZuR6P0irvT/bAP0h4297D/Lka5nz2vE=
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4/go.mod h1:J8R0E7soOpQFVqFiFd7EKepXCPpINa2n2t2EqbEsXnY=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=

View File

@ -141,6 +141,7 @@ require (
// TODO: remove after merge of https://github.com/cosmos/cosmos-sdk/pull/15873 and tagging releases
replace (
cosmossdk.io/collections => ../../collections // TODO: remove me after collections v0.2.0 is released
cosmossdk.io/core => ../../core
cosmossdk.io/store => ../../store
cosmossdk.io/x/tx => ../../x/tx

View File

@ -37,8 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=

View File

@ -0,0 +1,52 @@
package simulation
import (
"bytes"
"fmt"
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
"github.com/cosmos/cosmos-sdk/types/kv"
)
func NewStoreDecoderFuncFromCollectionsSchema(schema collections.Schema) func(kvA, kvB kv.Pair) string {
colls := schema.ListCollections()
prefixes := make([][]byte, len(colls))
valueCodecs := make([]collcodec.UntypedValueCodec, len(colls))
for i, coll := range colls {
prefixes[i] = coll.GetPrefix()
valueCodecs[i] = coll.ValueCodec()
}
return func(kvA, kvB kv.Pair) string {
for i, prefix := range prefixes {
if bytes.HasPrefix(kvA.Key, prefix) {
if !bytes.HasPrefix(kvB.Key, prefix) {
panic(fmt.Sprintf("prefix mismatch, keyA has prefix %x (%s), but keyB does not %x (%s)", prefix, prefix, kvB.Key, kvB.Key))
}
vc := valueCodecs[i]
// unmarshal kvA.Value to the corresponding type
vA, err := vc.Decode(kvA.Value)
if err != nil {
panic(err)
}
// unmarshal kvB.Value to the corresponding type
vB, err := vc.Decode(kvB.Value)
if err != nil {
panic(err)
}
vAString, err := vc.Stringify(vA)
if err != nil {
panic(err)
}
vBString, err := vc.Stringify(vB)
if err != nil {
panic(err)
}
return vAString + "\n" + vBString
}
}
panic(fmt.Errorf("unexpected key %X (%s)", kvA.Key, kvA.Key))
}
}

View File

@ -0,0 +1,71 @@
package simulation
import (
"testing"
"cosmossdk.io/collections"
"cosmossdk.io/collections/colltest"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/stretchr/testify/require"
)
func TestNewStoreDecoderFuncFromCollectionsSchema(t *testing.T) {
store, _ := colltest.MockStore()
sb := collections.NewSchemaBuilder(store)
prefixM1 := collections.NewPrefix("map_1")
prefixM2 := collections.NewPrefix("map_2")
m1 := collections.NewMap(sb, prefixM1, "map_1", collections.StringKey, collections.StringValue)
m2 := collections.NewMap(sb, prefixM2, "map_2", collections.Int32Key, collections.Int32Value)
schema, err := sb.Build()
require.NoError(t, err)
// create a new store decoder function from the schema
dec := NewStoreDecoderFuncFromCollectionsSchema(schema)
key1M1, err := collections.EncodeKeyWithPrefix(prefixM1, m1.KeyCodec(), "key_1")
require.NoError(t, err)
key2M1, err := collections.EncodeKeyWithPrefix(prefixM1, m1.KeyCodec(), "key_2")
require.NoError(t, err)
key1M2, err := collections.EncodeKeyWithPrefix(prefixM2, m2.KeyCodec(), int32(1))
require.NoError(t, err)
key2M2, err := collections.EncodeKeyWithPrefix(prefixM2, m2.KeyCodec(), int32(2))
require.NoError(t, err)
storeDec1 := dec(kv.Pair{
Key: key1M1,
Value: []byte("value_1"),
}, kv.Pair{
Key: key2M1,
Value: []byte("value_2"),
})
require.Equal(t, "value_1\nvalue_2", storeDec1)
storeDec2 := dec(kv.Pair{
Key: key1M2,
Value: []byte{0, 0, 0, 1},
}, kv.Pair{
Key: key2M2,
Value: []byte{0, 0, 0, 2},
})
require.Equal(t, "-2147483647\n-2147483646", storeDec2)
// test key conflict
require.Panics(t, func() {
dec(
kv.Pair{Key: append(prefixM1.Bytes(), 0x1)},
kv.Pair{Key: append(prefixM2.Bytes(), 0x1)},
)
}, "must panic when keys do not have the same prefix")
require.Panics(t, func() {
dec(
kv.Pair{Key: []byte("unknown_1")},
kv.Pair{Key: []byte("unknown_2")},
)
}, "must panic on unknown prefixes")
}

View File

@ -172,6 +172,6 @@ func (ms multiSource) Int63() (r int64) {
return r
}
func (ms multiSource) Seed(seed int64) {
func (ms multiSource) Seed(_ int64) {
panic("multiSource Seed should not be called")
}

View File

@ -190,7 +190,9 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight
}
// RegisterStoreDecoder registers a decoder for supply module's types
func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {}
func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {
sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.(keeper.BaseKeeper).Schema)
}
// WeightedOperations returns the all the gov module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation {

View File

@ -160,6 +160,8 @@ require (
replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.1
replace (
// TODO: remove me when collections v0.2.0 is released
cosmossdk.io/collections => ../../collections
cosmossdk.io/core => ../../core
cosmossdk.io/store => ../../store
cosmossdk.io/x/tx => ../tx

View File

@ -37,8 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cosmossdk.io/api v0.4.1 h1:0ikaYM6GyxTYYcfBiyR8YnLCfhNnhKpEFnaSepCTmqg=
cosmossdk.io/api v0.4.1/go.mod h1:jR7k5ok90LxW2lFUXvd8Vpo/dr4PpiyVegxdm7b1ZdE=
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
cosmossdk.io/errors v1.0.0-beta.7.0.20230429155654-3ee8242364e4 h1:rOy7iw7HlwKc5Af5qIHLXdBx/F98o6du/I/WGwOW6eA=