From 8793088e93db91b2f47e6f273e9693c826eea4d1 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Tue, 27 Jun 2023 12:45:43 +0200 Subject: [PATCH 01/12] add alternative value codec --- collections/codec/alternative_value.go | 47 +++++++++++++++++++ collections/codec/alternative_value_test.go | 52 +++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 collections/codec/alternative_value.go create mode 100644 collections/codec/alternative_value_test.go diff --git a/collections/codec/alternative_value.go b/collections/codec/alternative_value.go new file mode 100644 index 000000000000..714a3dd9d96b --- /dev/null +++ b/collections/codec/alternative_value.go @@ -0,0 +1,47 @@ +package codec + +// NewAltValueCodec returns a new AltValueCodec. canonicalValueCodec is the codec that you want the value +// to be encoded and decoded as, alternativeDecoder is a function that will attempt to decode the value +// in case the canonicalValueCodec fails to decode it. +func NewAltValueCodec[V any](canonicalValueCodec ValueCodec[V], alternativeDecoder func([]byte) (V, error)) ValueCodec[V] { + return AltValueCodec[V]{ + canonicalValueCodec: canonicalValueCodec, + alternativeDecoder: alternativeDecoder, + } +} + +// AltValueCodec is a codec that can decode a value from state in an alternative format. +// This is useful for migrating data from one format to another. For example, in x/bank +// balances were initially encoded as sdk.Coin, now they are encoded as math.Int. +// The AltValueCodec will be trying to decode the value as math.Int, and if that fails, +// it will attempt to decode it as sdk.Coin. +// NOTE: if the canonical format can also decode the alternative format, then this codec +// will produce undefined and undesirable behaviour. +type AltValueCodec[V any] struct { + canonicalValueCodec ValueCodec[V] + alternativeDecoder func([]byte) (V, error) +} + +// Decode will attempt to decode the value from state using the canonical value codec. +// If it fails to decode, it will attempt to decode the value using the alternative decoder. +func (a AltValueCodec[V]) Decode(b []byte) (V, error) { + v, err := a.canonicalValueCodec.Decode(b) + if err != nil { + return a.alternativeDecoder(b) + } + return v, nil +} + +// Below there is the implementation of ValueCodec relying on the canonical value codec. + +func (a AltValueCodec[V]) Encode(value V) ([]byte, error) { return a.canonicalValueCodec.Encode(value) } + +func (a AltValueCodec[V]) EncodeJSON(value V) ([]byte, error) { + return a.canonicalValueCodec.EncodeJSON(value) +} + +func (a AltValueCodec[V]) DecodeJSON(b []byte) (V, error) { return a.canonicalValueCodec.DecodeJSON(b) } + +func (a AltValueCodec[V]) Stringify(value V) string { return a.canonicalValueCodec.Stringify(value) } + +func (a AltValueCodec[V]) ValueType() string { return a.canonicalValueCodec.ValueType() } diff --git a/collections/codec/alternative_value_test.go b/collections/codec/alternative_value_test.go new file mode 100644 index 000000000000..a5064609107f --- /dev/null +++ b/collections/codec/alternative_value_test.go @@ -0,0 +1,52 @@ +package codec_test + +import ( + "encoding/json" + "testing" + + "cosmossdk.io/collections/codec" + "cosmossdk.io/collections/colltest" + "github.com/stretchr/testify/require" +) + +type altValue struct { + Value uint64 `json:"value"` +} + +func TestAltValueCodec(t *testing.T) { + // we assume we want to migrate the value from json(altValue) to just be + // the raw value uint64. + canonical := codec.KeyToValueCodec(codec.NewUint64Key[uint64]()) + alternative := func(v []byte) (uint64, error) { + var alt altValue + err := json.Unmarshal(v, &alt) + if err != nil { + return 0, err + } + return alt.Value, nil + } + + cdc := codec.NewAltValueCodec(canonical, alternative) + + t.Run("decodes alternative value", func(t *testing.T) { + expected := uint64(100) + alternativeEncodedBytes, err := json.Marshal(altValue{Value: expected}) + require.NoError(t, err) + got, err := cdc.Decode(alternativeEncodedBytes) + require.NoError(t, err) + require.Equal(t, expected, got) + }) + + t.Run("decodes canonical value", func(t *testing.T) { + expected := uint64(100) + canonicalEncodedBytes, err := cdc.Encode(expected) + require.NoError(t, err) + got, err := cdc.Decode(canonicalEncodedBytes) + require.NoError(t, err) + require.Equal(t, expected, got) + }) + + t.Run("conformance", func(t *testing.T) { + colltest.TestValueCodec(t, cdc, uint64(100)) + }) +} From 3507c75b498b29cd5be8f0ecb4327b4d42a3b965 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Thu, 29 Jun 2023 16:42:54 +0200 Subject: [PATCH 02/12] cleanups --- collections/codec/bool.go | 2 +- collections/colltest/codec.go | 5 +++ collections/indexes/multi.go | 28 +++++++++++++- collections/indexes/multi_test.go | 49 ++++++++++++++++++++++++ collections/indexes/reverse_pair.go | 29 +++++++++++++- collections/indexes/reverse_pair_test.go | 30 +++++++++++++++ collections/keyset.go | 36 +++++++++++++++-- collections/keyset_test.go | 31 +++++++++++++++ 8 files changed, 203 insertions(+), 7 deletions(-) diff --git a/collections/codec/bool.go b/collections/codec/bool.go index 4016c8d68dcd..827af36c0715 100644 --- a/collections/codec/bool.go +++ b/collections/codec/bool.go @@ -33,7 +33,7 @@ func (b boolKey[T]) Decode(buffer []byte) (int, T, error) { } } -func (b boolKey[T]) Size(key T) int { return 1 } +func (b boolKey[T]) Size(_ T) int { return 1 } func (b boolKey[T]) EncodeJSON(value T) ([]byte, error) { return json.Marshal(value) diff --git a/collections/colltest/codec.go b/collections/colltest/codec.go index c0bb5038199c..58eb6f4ce446 100644 --- a/collections/colltest/codec.go +++ b/collections/colltest/codec.go @@ -40,6 +40,11 @@ func TestKeyCodec[T any](t *testing.T, keyCodec codec.KeyCodec[T], key T) { decoded, err := keyCodec.DecodeJSON(keyJSON) require.NoError(t, err) require.Equal(t, key, decoded, "json encoding and decoding did not produce the same results") + + // check type + require.NotEmpty(t, keyCodec.KeyType()) + // check string + _ = keyCodec.Stringify(key) } // TestValueCodec asserts the correct behavior of a ValueCodec over the type T. diff --git a/collections/indexes/multi.go b/collections/indexes/multi.go index 9e567aaab425..bb38bd38f9bf 100644 --- a/collections/indexes/multi.go +++ b/collections/indexes/multi.go @@ -8,6 +8,21 @@ import ( "cosmossdk.io/collections/codec" ) +type multiOptions struct { + uncheckedValue bool +} + +// WithMultiUncheckedValue is an option that can be passed to NewMulti to +// ignore index values different from '[]byte{}' and continue with the operation. +// This should be used only to behave nicely in case you have used values different +// from '[]byte{}' in your storage before migrating to collections. Refer to +// WithKeySetUncheckedValue for more information. +func WithMultiUncheckedValue() func(*multiOptions) { + return func(o *multiOptions) { + o.uncheckedValue = true + } +} + // Multi defines the most common index. It can be used to create a reference between // a field of value and its primary key. Multiple primary keys can be mapped to the same // reference key as the index does not enforce uniqueness constraints. @@ -27,10 +42,21 @@ func NewMulti[ReferenceKey, PrimaryKey, Value any]( refCodec codec.KeyCodec[ReferenceKey], pkCodec codec.KeyCodec[PrimaryKey], getRefKeyFunc func(pk PrimaryKey, value Value) (ReferenceKey, error), + options ...func(*multiOptions), ) *Multi[ReferenceKey, PrimaryKey, Value] { + ks := collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)) + + o := new(multiOptions) + for _, opt := range options { + opt(o) + } + if o.uncheckedValue { + ks = collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec), collections.WithKeySetUncheckedValue()) + } + return &Multi[ReferenceKey, PrimaryKey, Value]{ getRefKey: getRefKeyFunc, - refKeys: collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)), + refKeys: ks, } } diff --git a/collections/indexes/multi_test.go b/collections/indexes/multi_test.go index 428b880bfb71..73521ee03e38 100644 --- a/collections/indexes/multi_test.go +++ b/collections/indexes/multi_test.go @@ -61,3 +61,52 @@ func TestMultiIndex(t *testing.T) { require.False(t, iter.Valid()) require.NoError(t, iter.Close()) } + +func TestMultiUnchecked(t *testing.T) { + sk, ctx := deps() + schema := collections.NewSchemaBuilder(sk) + + uncheckedMi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) { + return value.City, nil + }, WithMultiUncheckedValue()) + + mi := NewMulti(schema, collections.NewPrefix("prefix"), "multi_index", collections.StringKey, collections.Uint64Key, func(_ uint64, value company) (string, error) { + return value.City, nil + }) + + rawKey, err := collections.EncodeKeyWithPrefix( + collections.NewPrefix("prefix"), + uncheckedMi.KeyCodec(), + collections.Join("milan", uint64(2))) + require.NoError(t, err) + + // set value to be something different from []byte{} + require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("something"))) + + // normal multi index will fail. + err = mi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) { + return true, err + }) + require.ErrorIs(t, err, collections.ErrEncoding) + + // unchecked multi index will not fail. + err = uncheckedMi.Walk(ctx, nil, func(indexingKey string, indexedKey uint64) (stop bool, err error) { + require.Equal(t, "milan", indexingKey) + require.Equal(t, uint64(2), indexedKey) + return true, err + }) + require.NoError(t, err) + + // unchecked multi will also reset the value + err = mi.Reference(ctx, 2, company{City: "milan"}, func() (company, error) { + return company{ + City: "milan", + }, nil + }) + require.NoError(t, err) + + // value reset to []byte{} + rawValue, err := sk.OpenKVStore(ctx).Get(rawKey) + require.NoError(t, err) + require.Equal(t, []byte{}, rawValue) +} diff --git a/collections/indexes/reverse_pair.go b/collections/indexes/reverse_pair.go index 243b2d289ad8..ed8bbb652f20 100644 --- a/collections/indexes/reverse_pair.go +++ b/collections/indexes/reverse_pair.go @@ -7,6 +7,21 @@ import ( "cosmossdk.io/collections/codec" ) +type reversePairOptions struct { + uncheckedValue bool +} + +// WithReversePairUncheckedValue is an option that can be passed to NewReversePair to +// ignore index values different from '[]byte{}' and continue with the operation. +// This should be used only if you are migrating to collections and have used a different +// placeholder value in your storage index keys. +// Refer to WithKeySetUncheckedValue for more information. +func WithReversePairUncheckedValue() func(*reversePairOptions) { + return func(o *reversePairOptions) { + o.uncheckedValue = true + } +} + // ReversePair is an index that is used with collections.Pair keys. It indexes objects by their second part of the key. // When the value is being indexed by collections.IndexedMap then ReversePair will create a relationship between // the second part of the primary key and the first part. @@ -31,10 +46,20 @@ func NewReversePair[Value, K1, K2 any]( prefix collections.Prefix, name string, pairCodec codec.KeyCodec[collections.Pair[K1, K2]], + options ...func(*reversePairOptions), ) *ReversePair[K1, K2, Value] { pkc := pairCodec.(pairKeyCodec[K1, K2]) + ks := collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())) + o := new(reversePairOptions) + for _, option := range options { + option(o) + } + if o.uncheckedValue { + ks = collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()), collections.WithKeySetUncheckedValue()) + } + mi := &ReversePair[K1, K2, Value]{ - refKeys: collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())), + refKeys: ks, } return mi @@ -67,7 +92,7 @@ func (i *ReversePair[K1, K2, Value]) Unreference(ctx context.Context, pk collect func (i *ReversePair[K1, K2, Value]) Walk( ctx context.Context, ranger collections.Ranger[collections.Pair[K2, K1]], - walkFunc func(indexingKey K2, indexedKey K1) (stop bool, err error), + walkFunc func(indexedKey K2, indexingKey K1) (stop bool, err error), ) error { return i.refKeys.Walk(ctx, ranger, func(key collections.Pair[K2, K1]) (bool, error) { return walkFunc(key.K1(), key.K2()) diff --git a/collections/indexes/reverse_pair_test.go b/collections/indexes/reverse_pair_test.go index e4036aaaafdd..ff8634d0a427 100644 --- a/collections/indexes/reverse_pair_test.go +++ b/collections/indexes/reverse_pair_test.go @@ -67,3 +67,33 @@ func TestReversePair(t *testing.T) { _, err = indexedMap.Indexes.Denom.MatchExact(ctx, "atom") require.ErrorIs(t, collections.ErrInvalidIterator, err) } + +func TestUncheckedReversePair(t *testing.T) { + sk, ctx := deps() + sb := collections.NewSchemaBuilder(sk) + // we create an indexed map that maps balances, which are saved as + // key: Pair[Address, Denom] + // value: Amount + keyCodec := collections.PairKeyCodec(collections.StringKey, collections.StringKey) + + uncheckedRp := NewReversePair[Amount](sb, collections.NewPrefix("denom_index"), "denom_index", keyCodec, WithReversePairUncheckedValue()) + rp := NewReversePair[Amount](sb, collections.NewPrefix("denom_index"), "denom_index", keyCodec) + + rawKey, err := collections.EncodeKeyWithPrefix(collections.NewPrefix("denom_index"), uncheckedRp.KeyCodec(), collections.Join("address1", "atom")) + require.NoError(t, err) + + require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("i should not be here"))) + + // normal reverse pair fails + err = rp.Walk(ctx, nil, func(denom string, address string) (bool, error) { + return false, nil + }) + require.ErrorIs(t, err, collections.ErrEncoding) + + // unchecked reverse pair succeeds + err = uncheckedRp.Walk(ctx, nil, func(indexingKey string, indexedKey string) (stop bool, err error) { + t.Log(indexingKey, indexedKey) + return false, nil + }) + require.NoError(t, err) +} diff --git a/collections/keyset.go b/collections/keyset.go index 74f7d540443e..b056ac71ff7b 100644 --- a/collections/keyset.go +++ b/collections/keyset.go @@ -8,14 +8,43 @@ import ( "cosmossdk.io/collections/codec" ) +// WithKeySetUncheckedValue changes the behaviour of the KeySet when it encounters +// a value different from '[]byte{}', by default the KeySet errors when this happens. +// This option allows to ignore the value and continue with the operation, in turn +// the value will be cleared out and set to '[]byte{}'. +// You should never use this option if you're creating a new state object from scratch. +// This should be used only to behave nicely in case you have used values different +// from '[]byte{}' in your storage before migrating to collections. +func WithKeySetUncheckedValue() func(opt *keySetOptions) { + return func(opt *keySetOptions) { + opt.uncheckedValue = true + } +} + +type keySetOptions struct{ uncheckedValue bool } + // KeySet builds on top of a Map and represents a collection retaining only a set // of keys and no value. It can be used, for example, in an allow list. type KeySet[K any] Map[K, NoValue] // NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection // and a KeyCodec for the key K. -func NewKeySet[K any](schema *SchemaBuilder, prefix Prefix, name string, keyCodec codec.KeyCodec[K]) KeySet[K] { - return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, noValueCodec)) +func NewKeySet[K any]( + schema *SchemaBuilder, + prefix Prefix, + name string, + keyCodec codec.KeyCodec[K], + options ...func(opt *keySetOptions), +) KeySet[K] { + o := new(keySetOptions) + for _, opt := range options { + opt(o) + } + vc := noValueCodec + if o.uncheckedValue { + vc = codec.NewAltValueCodec(vc, func(_ []byte) (NoValue, error) { return NoValue{}, nil }) + } + return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, vc)) } // Set adds the key to the KeySet. Errors on encoding problems. @@ -79,6 +108,7 @@ var noValueCodec codec.ValueCodec[NoValue] = NoValue{} const noValueValueType = "no_value" +// NoValue is a type that can be used to represent a non-existing value. type NoValue struct{} func (n NoValue) EncodeJSON(_ NoValue) ([]byte, error) { @@ -98,7 +128,7 @@ func (NoValue) Encode(_ NoValue) ([]byte, error) { func (NoValue) Decode(b []byte) (NoValue, error) { if !bytes.Equal(b, []byte{}) { - return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice", ErrEncoding) + return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice, got: %x", ErrEncoding, b) } return NoValue{}, nil } diff --git a/collections/keyset_test.go b/collections/keyset_test.go index 6fe3e7eedfb0..cabc2ead865b 100644 --- a/collections/keyset_test.go +++ b/collections/keyset_test.go @@ -67,3 +67,34 @@ func Test_noValue(t *testing.T) { _, err = noValueCodec.Decode([]byte("bad")) require.ErrorIs(t, err, ErrEncoding) } + +func TestUncheckedKeySet(t *testing.T) { + sk, ctx := deps() + schema := NewSchemaBuilder(sk) + uncheckedKs := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey, WithKeySetUncheckedValue()) + ks := NewKeySet(schema, NewPrefix("keyset"), "keyset", StringKey) + // we set a NoValue unfriendly value. + require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset1"), []byte("A"))) + require.NoError(t, sk.OpenKVStore(ctx).Set([]byte("keyset2"), []byte("B"))) + + // the standard KeySet errors here, because it doesn't like the fact that the value is []byte("A") + // and not []byte{}. + err := ks.Walk(ctx, nil, func(key string) (stop bool, err error) { + return true, nil + }) + require.ErrorIs(t, err, ErrEncoding) + + // the unchecked KeySet doesn't care about the value, so it works. + err = uncheckedKs.Walk(ctx, nil, func(key string) (stop bool, err error) { + require.Equal(t, "1", key) + return true, nil + }) + require.NoError(t, err) + + // now we set it again + require.NoError(t, uncheckedKs.Set(ctx, "1")) + // and we will see that the value which was []byte("A") has been cleared to be []byte{} + raw, err := sk.OpenKVStore(ctx).Get([]byte("keyset1")) + require.NoError(t, err) + require.Equal(t, []byte{}, raw) +} From 6942aa9e1ec9977b58ca90dcddd8ecccf1c8c4d5 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Thu, 29 Jun 2023 17:09:36 +0200 Subject: [PATCH 03/12] fix some things --- collections/indexes/reverse_pair.go | 2 +- collections/indexes/reverse_pair_test.go | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/collections/indexes/reverse_pair.go b/collections/indexes/reverse_pair.go index ed8bbb652f20..f0c5ac8dee43 100644 --- a/collections/indexes/reverse_pair.go +++ b/collections/indexes/reverse_pair.go @@ -92,7 +92,7 @@ func (i *ReversePair[K1, K2, Value]) Unreference(ctx context.Context, pk collect func (i *ReversePair[K1, K2, Value]) Walk( ctx context.Context, ranger collections.Ranger[collections.Pair[K2, K1]], - walkFunc func(indexedKey K2, indexingKey K1) (stop bool, err error), + walkFunc func(indexingKey K2, indexedKey K1) (stop bool, err error), ) error { return i.refKeys.Walk(ctx, ranger, func(key collections.Pair[K2, K1]) (bool, error) { return walkFunc(key.K1(), key.K2()) diff --git a/collections/indexes/reverse_pair_test.go b/collections/indexes/reverse_pair_test.go index ff8634d0a427..0d19abc40639 100644 --- a/collections/indexes/reverse_pair_test.go +++ b/collections/indexes/reverse_pair_test.go @@ -71,15 +71,13 @@ func TestReversePair(t *testing.T) { func TestUncheckedReversePair(t *testing.T) { sk, ctx := deps() sb := collections.NewSchemaBuilder(sk) - // we create an indexed map that maps balances, which are saved as - // key: Pair[Address, Denom] - // value: Amount + prefix := collections.NewPrefix("prefix") keyCodec := collections.PairKeyCodec(collections.StringKey, collections.StringKey) - uncheckedRp := NewReversePair[Amount](sb, collections.NewPrefix("denom_index"), "denom_index", keyCodec, WithReversePairUncheckedValue()) - rp := NewReversePair[Amount](sb, collections.NewPrefix("denom_index"), "denom_index", keyCodec) + uncheckedRp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec, WithReversePairUncheckedValue()) + rp := NewReversePair[Amount](sb, prefix, "denom_index", keyCodec) - rawKey, err := collections.EncodeKeyWithPrefix(collections.NewPrefix("denom_index"), uncheckedRp.KeyCodec(), collections.Join("address1", "atom")) + rawKey, err := collections.EncodeKeyWithPrefix(prefix, uncheckedRp.KeyCodec(), collections.Join("atom", "address1")) require.NoError(t, err) require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("i should not be here"))) @@ -92,8 +90,15 @@ func TestUncheckedReversePair(t *testing.T) { // unchecked reverse pair succeeds err = uncheckedRp.Walk(ctx, nil, func(indexingKey string, indexedKey string) (stop bool, err error) { - t.Log(indexingKey, indexedKey) - return false, nil + require.Equal(t, "atom", indexingKey) + return true, nil }) require.NoError(t, err) + + // unchecked reverse pair lazily updates + err = uncheckedRp.Reference(ctx, collections.Join("address1", "atom"), 0, nil) + require.NoError(t, err) + rawValue, err := sk.OpenKVStore(ctx).Get(rawKey) + require.NoError(t, err) + require.Equal(t, []byte{}, rawValue) } From be9810a3e9375198071cf37ededd67ba87e24b8c Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Thu, 29 Jun 2023 17:14:10 +0200 Subject: [PATCH 04/12] lint --- collections/codec/alternative_value.go | 2 +- collections/codec/alternative_value_test.go | 3 ++- collections/indexes/reverse_pair_test.go | 4 ++-- collections/keyset.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/collections/codec/alternative_value.go b/collections/codec/alternative_value.go index 714a3dd9d96b..4d7902a4be33 100644 --- a/collections/codec/alternative_value.go +++ b/collections/codec/alternative_value.go @@ -16,7 +16,7 @@ func NewAltValueCodec[V any](canonicalValueCodec ValueCodec[V], alternativeDecod // The AltValueCodec will be trying to decode the value as math.Int, and if that fails, // it will attempt to decode it as sdk.Coin. // NOTE: if the canonical format can also decode the alternative format, then this codec -// will produce undefined and undesirable behaviour. +// will produce undefined and undesirable behavior. type AltValueCodec[V any] struct { canonicalValueCodec ValueCodec[V] alternativeDecoder func([]byte) (V, error) diff --git a/collections/codec/alternative_value_test.go b/collections/codec/alternative_value_test.go index a5064609107f..358395427b90 100644 --- a/collections/codec/alternative_value_test.go +++ b/collections/codec/alternative_value_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/require" + "cosmossdk.io/collections/codec" "cosmossdk.io/collections/colltest" - "github.com/stretchr/testify/require" ) type altValue struct { diff --git a/collections/indexes/reverse_pair_test.go b/collections/indexes/reverse_pair_test.go index 0d19abc40639..564b273fa284 100644 --- a/collections/indexes/reverse_pair_test.go +++ b/collections/indexes/reverse_pair_test.go @@ -83,13 +83,13 @@ func TestUncheckedReversePair(t *testing.T) { require.NoError(t, sk.OpenKVStore(ctx).Set(rawKey, []byte("i should not be here"))) // normal reverse pair fails - err = rp.Walk(ctx, nil, func(denom string, address string) (bool, error) { + err = rp.Walk(ctx, nil, func(denom, address string) (bool, error) { return false, nil }) require.ErrorIs(t, err, collections.ErrEncoding) // unchecked reverse pair succeeds - err = uncheckedRp.Walk(ctx, nil, func(indexingKey string, indexedKey string) (stop bool, err error) { + err = uncheckedRp.Walk(ctx, nil, func(indexingKey, indexedKey string) (stop bool, err error) { require.Equal(t, "atom", indexingKey) return true, nil }) diff --git a/collections/keyset.go b/collections/keyset.go index b056ac71ff7b..37c3bd268e37 100644 --- a/collections/keyset.go +++ b/collections/keyset.go @@ -8,7 +8,7 @@ import ( "cosmossdk.io/collections/codec" ) -// WithKeySetUncheckedValue changes the behaviour of the KeySet when it encounters +// WithKeySetUncheckedValue changes the behavior of the KeySet when it encounters // a value different from '[]byte{}', by default the KeySet errors when this happens. // This option allows to ignore the value and continue with the operation, in turn // the value will be cleared out and set to '[]byte{}'. From cdd6e09da2f145fdc2b6c3176d46c9ffaed37844 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Thu, 29 Jun 2023 17:16:39 +0200 Subject: [PATCH 05/12] CHANGELOG.md --- collections/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/collections/CHANGELOG.md b/collections/CHANGELOG.md index 39169cdb5c3b..91fd55125ddd 100644 --- a/collections/CHANGELOG.md +++ b/collections/CHANGELOG.md @@ -34,6 +34,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features * [#16074](https://github.com/cosmos/cosmos-sdk/pull/16607) - Introduces `Clear` method for `Map` and `KeySet` +* [#16773](https://github.com/cosmos/cosmos-sdk/pull/16773) + * Adds `AltValueCodec` which provides a way to decode a value in two ways. + * Adds the possibility to specify an alternative way to decode the values of `KeySet`, `indexes.Multi`, `indexes.ReversePair`. ## [v0.2.0](https://github.com/cosmos/cosmos-sdk/releases/tag/collections%2Fv0.2.0) From d0ce9f7772187827d71f44eec5daceef432bee3d Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Thu, 29 Jun 2023 23:23:36 +0200 Subject: [PATCH 06/12] more docs --- collections/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/collections/README.md b/collections/README.md index 7f8278233c64..bd25d2e1cccf 100644 --- a/collections/README.md +++ b/collections/README.md @@ -1117,3 +1117,30 @@ func (k Keeper) GetAccount(ctx sdk.context, addr sdk.AccAddress) (sdk.AccountI, return k.Accounts.Get(ctx, addr) } ``` + +## Advanced Usages + +### Alternative Value Codec + +The `codec.AltValueCodec` allows a collection to decode values using a different codec than the one used to encode them. +Basically it enables to decode two different byte representations of the same concrete value. +It can be used to lazily migrate values from one bytes representation to another, as long as the new representation is +not able to decode the old one. + +A concrete example can be found in `x/bank` where the balance was initially stored as `Coin` and then migrated to `Int`. + +```go + +var BankBalanceValueCodec = codec.NewAltValueCodec(sdk.IntValue, func(b []byte) (sdk.Int, error) { + coin := sdk.Coin{} + err := coin.Unmarshal(b) + if err != nil { + return sdk.Int{}, err + } + return coin.Amount, nil +}) +``` + +The above example shows how to create an `AltValueCodec` that can decode both `sdk.Int` and `sdk.Coin` values. The provided +decoder function will be used as a fallback in case the default decoder fails. When the value will be encoded back into state +it will use the default encoder. This allows to lazily migrate values to a new bytes representation. \ No newline at end of file From 886a8349ad85f2992b50007227567a3cd47da98a Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Fri, 30 Jun 2023 12:44:54 +0200 Subject: [PATCH 07/12] address facu review --- collections/indexes/multi.go | 9 +++++---- collections/indexes/reverse_pair.go | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/collections/indexes/multi.go b/collections/indexes/multi.go index bb38bd38f9bf..49ad2fb6f378 100644 --- a/collections/indexes/multi.go +++ b/collections/indexes/multi.go @@ -44,19 +44,20 @@ func NewMulti[ReferenceKey, PrimaryKey, Value any]( getRefKeyFunc func(pk PrimaryKey, value Value) (ReferenceKey, error), options ...func(*multiOptions), ) *Multi[ReferenceKey, PrimaryKey, Value] { - ks := collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)) - o := new(multiOptions) for _, opt := range options { opt(o) } if o.uncheckedValue { - ks = collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec), collections.WithKeySetUncheckedValue()) + return &Multi[ReferenceKey, PrimaryKey, Value]{ + getRefKey: getRefKeyFunc, + refKeys: collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec), collections.WithKeySetUncheckedValue()), + } } return &Multi[ReferenceKey, PrimaryKey, Value]{ getRefKey: getRefKeyFunc, - refKeys: ks, + refKeys: collections.NewKeySet(schema, prefix, name, collections.PairKeyCodec(refCodec, pkCodec)), } } diff --git a/collections/indexes/reverse_pair.go b/collections/indexes/reverse_pair.go index f0c5ac8dee43..0e2cc301374e 100644 --- a/collections/indexes/reverse_pair.go +++ b/collections/indexes/reverse_pair.go @@ -49,17 +49,18 @@ func NewReversePair[Value, K1, K2 any]( options ...func(*reversePairOptions), ) *ReversePair[K1, K2, Value] { pkc := pairCodec.(pairKeyCodec[K1, K2]) - ks := collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())) o := new(reversePairOptions) for _, option := range options { option(o) } if o.uncheckedValue { - ks = collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()), collections.WithKeySetUncheckedValue()) + return &ReversePair[K1, K2, Value]{ + refKeys: collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1()), collections.WithKeySetUncheckedValue()), + } } mi := &ReversePair[K1, K2, Value]{ - refKeys: ks, + refKeys: collections.NewKeySet(sb, prefix, name, collections.PairKeyCodec(pkc.KeyCodec2(), pkc.KeyCodec1())), } return mi From 8de0778fa4e6d397c3db584c34b3c11d36a22ba7 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Sun, 2 Jul 2023 13:16:18 +0200 Subject: [PATCH 08/12] maintain compatibility in bank balance --- go.mod | 2 +- go.sum | 4 +- simapp/go.mod | 2 +- simapp/go.sum | 4 +- tests/go.mod | 2 +- tests/go.sum | 4 +- x/bank/keeper/collections_test.go | 84 +++++++++++++++++++++++++++++++ x/bank/keeper/view.go | 3 +- x/bank/types/keys.go | 24 ++------- x/bank/types/keys_test.go | 5 +- 10 files changed, 102 insertions(+), 32 deletions(-) create mode 100644 x/bank/keeper/collections_test.go diff --git a/go.mod b/go.mod index c721b3fc8ad2..a7a6f0eb2a33 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/cosmos/cosmos-sdk require ( cosmossdk.io/api v0.4.2 - cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 + cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 cosmossdk.io/core v0.8.0 cosmossdk.io/depinject v1.0.0-alpha.3 cosmossdk.io/errors v1.0.0-beta.7.0.20230524212735-6cabb6aa5741 diff --git a/go.sum b/go.sum index 413458510042..deba9228de63 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ 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.2 h1:lQBMl4xINnMnBOR/tQLtjlDnR4exr4e6/SfHR8PILE0= cosmossdk.io/api v0.4.2/go.mod h1:qrVgOp7DIeAXa+Tt5dDjOC47bZCDrwx8ZHxrmy7STNE= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 h1:6CxleI/IgdENrujwTY2yY9Wg52DVZr4eq4L71ANoLuQ= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531/go.mod h1:k8IKBKC/lO+BKoIGae3RC8NCBV8+7JaAw+es51YylFs= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 h1:lwpUvyDp1RfjjiuD/D3qzcApePggjppLLFED82dL+s4= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85/go.mod h1:jr4jlswIpUC2KwocukvZQHj32O6EDCuEMVTsQ87ZP2c= cosmossdk.io/core v0.8.0 h1:LcJnu52E1a8f8E317VfQ1xK/RZe+IuhMNQAjnDLh25M= cosmossdk.io/core v0.8.0/go.mod h1:LF6VLOv2DdCiaHxYVmr0MZcZpaSM9ZgvyrQSYTeg6D0= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/simapp/go.mod b/simapp/go.mod index 018aca3c02e1..10313007ac72 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -38,7 +38,7 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/storage v1.30.0 // indirect - cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 // indirect + cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 // indirect cosmossdk.io/errors v1.0.0-beta.7.0.20230524212735-6cabb6aa5741 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index d4ce24bc09d3..e8443defa2bf 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -190,8 +190,8 @@ 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/api v0.4.3-0.20230615032830-feb87fce5495 h1:wreIRQKuKccFCOI4TWoVy/6tQNiDX4ms7VKNrXP4DEM= cosmossdk.io/api v0.4.3-0.20230615032830-feb87fce5495/go.mod h1:qrVgOp7DIeAXa+Tt5dDjOC47bZCDrwx8ZHxrmy7STNE= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 h1:6CxleI/IgdENrujwTY2yY9Wg52DVZr4eq4L71ANoLuQ= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531/go.mod h1:k8IKBKC/lO+BKoIGae3RC8NCBV8+7JaAw+es51YylFs= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 h1:lwpUvyDp1RfjjiuD/D3qzcApePggjppLLFED82dL+s4= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85/go.mod h1:jr4jlswIpUC2KwocukvZQHj32O6EDCuEMVTsQ87ZP2c= cosmossdk.io/core v0.8.0 h1:LcJnu52E1a8f8E317VfQ1xK/RZe+IuhMNQAjnDLh25M= cosmossdk.io/core v0.8.0/go.mod h1:LF6VLOv2DdCiaHxYVmr0MZcZpaSM9ZgvyrQSYTeg6D0= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/tests/go.mod b/tests/go.mod index 44c04b901d97..ca97d8309e72 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( cosmossdk.io/api v0.4.3-0.20230615032830-feb87fce5495 - cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 + cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 cosmossdk.io/core v0.8.0 cosmossdk.io/depinject v1.0.0-alpha.3 cosmossdk.io/errors v1.0.0-beta.7.0.20230524212735-6cabb6aa5741 diff --git a/tests/go.sum b/tests/go.sum index 05f1c7c7680a..7daed551d327 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -190,8 +190,8 @@ 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/api v0.4.3-0.20230615032830-feb87fce5495 h1:wreIRQKuKccFCOI4TWoVy/6tQNiDX4ms7VKNrXP4DEM= cosmossdk.io/api v0.4.3-0.20230615032830-feb87fce5495/go.mod h1:qrVgOp7DIeAXa+Tt5dDjOC47bZCDrwx8ZHxrmy7STNE= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531 h1:6CxleI/IgdENrujwTY2yY9Wg52DVZr4eq4L71ANoLuQ= -cosmossdk.io/collections v0.2.1-0.20230620134406-d4f1e88b6531/go.mod h1:k8IKBKC/lO+BKoIGae3RC8NCBV8+7JaAw+es51YylFs= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85 h1:lwpUvyDp1RfjjiuD/D3qzcApePggjppLLFED82dL+s4= +cosmossdk.io/collections v0.2.1-0.20230630104454-886a8349ad85/go.mod h1:jr4jlswIpUC2KwocukvZQHj32O6EDCuEMVTsQ87ZP2c= cosmossdk.io/core v0.8.0 h1:LcJnu52E1a8f8E317VfQ1xK/RZe+IuhMNQAjnDLh25M= cosmossdk.io/core v0.8.0/go.mod h1:LF6VLOv2DdCiaHxYVmr0MZcZpaSM9ZgvyrQSYTeg6D0= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/x/bank/keeper/collections_test.go b/x/bank/keeper/collections_test.go new file mode 100644 index 000000000000..413b71506a75 --- /dev/null +++ b/x/bank/keeper/collections_test.go @@ -0,0 +1,84 @@ +package keeper_test + +import ( + "testing" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttime "github.com/cometbft/cometbft/types/time" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "cosmossdk.io/collections" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func TestBankStateCompatibility(t *testing.T) { + key := storetypes.NewKVStoreKey(banktypes.StoreKey) + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) + encCfg := moduletestutil.MakeTestEncodingConfig() + + storeService := runtime.NewKVStoreService(key) + + // gomock initializations + ctrl := gomock.NewController(t) + authKeeper := banktestutil.NewMockAccountKeeper(ctrl) + authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes() + + k := keeper.NewBaseKeeper( + encCfg.Codec, + storeService, + authKeeper, + map[string]bool{accAddrs[4].String(): true}, + authtypes.NewModuleAddress(banktypes.GovModuleName).String(), + log.NewNopLogger(), + ) + + // test we can decode balances without problems + // using the old value format of the denom to address index + bankDenomAddressLegacyIndexValue := []byte{0} // taken from: https://github.com/cosmos/cosmos-sdk/blob/v0.47.3/x/bank/keeper/send.go#L361 + rawKey, err := collections.EncodeKeyWithPrefix( + banktypes.DenomAddressPrefix, + k.Balances.Indexes.Denom.KeyCodec(), + collections.Join("atom", sdk.AccAddress("test")), + ) + require.NoError(t, err) + // we set the index key to the old value. + require.NoError(t, storeService.OpenKVStore(ctx).Set(rawKey, bankDenomAddressLegacyIndexValue)) + + // test walking is ok + err = k.Balances.Indexes.Denom.Walk(ctx, nil, func(indexingKey string, indexedKey sdk.AccAddress) (stop bool, err error) { + require.Equal(t, indexedKey, sdk.AccAddress("test")) + require.Equal(t, indexingKey, "atom") + return true, nil + }) + require.NoError(t, err) + + // test matching is also ok + iter, err := k.Balances.Indexes.Denom.MatchExact(ctx, "atom") + require.NoError(t, err) + defer iter.Close() + pks, err := iter.PrimaryKeys() + require.NoError(t, err) + require.Len(t, pks, 1) + require.Equal(t, pks[0], collections.Join(sdk.AccAddress("test"), "atom")) + + // assert the index value will be updated to the new format + err = k.Balances.Indexes.Denom.Reference(ctx, collections.Join(sdk.AccAddress("test"), "atom"), sdk.ZeroInt(), nil) + require.NoError(t, err) + + newRawValue, err := storeService.OpenKVStore(ctx).Get(rawKey) + require.NoError(t, err) + require.Equal(t, []byte{}, newRawValue) +} diff --git a/x/bank/keeper/view.go b/x/bank/keeper/view.go index ea293047a9e4..89b434aa6c59 100644 --- a/x/bank/keeper/view.go +++ b/x/bank/keeper/view.go @@ -43,6 +43,7 @@ func newBalancesIndexes(sb *collections.SchemaBuilder) BalancesIndexes { Denom: indexes.NewReversePair[math.Int]( sb, types.DenomAddressPrefix, "address_by_denom_index", collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), collections.StringKey), // nolint:staticcheck // Note: refer to the LengthPrefixedAddressKey docs to understand why we do this. + indexes.WithReversePairUncheckedValue(), // denom to address indexes were stored as Key: Join(denom, address) Value: []byte{0}, this will migrate the value to []byte{} in a lazy way. ), } } @@ -81,7 +82,7 @@ func NewBaseViewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, Supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue), DenomMetadata: collections.NewMap(sb, types.DenomMetadataPrefix, "denom_metadata", collections.StringKey, codec.CollValue[types.Metadata](cdc)), SendEnabled: collections.NewMap(sb, types.SendEnabledPrefix, "send_enabled", collections.StringKey, codec.BoolValue), // NOTE: we use a bool value which uses protobuf to retain state backwards compat - Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), types.NewBalanceCompatValueCodec(), newBalancesIndexes(sb)), + Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), types.BalanceValueCodec, newBalancesIndexes(sb)), Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), } diff --git a/x/bank/types/keys.go b/x/bank/types/keys.go index 0611d77a1fea..0d0b60905a1f 100644 --- a/x/bank/types/keys.go +++ b/x/bank/types/keys.go @@ -44,27 +44,13 @@ var ( ParamsKey = collections.NewPrefix(5) ) -// NewBalanceCompatValueCodec is a codec for encoding Balances in a backwards compatible way -// with respect to the old format. -func NewBalanceCompatValueCodec() collcodec.ValueCodec[math.Int] { - return balanceCompatValueCodec{ - sdk.IntValue, - } -} - -type balanceCompatValueCodec struct { - collcodec.ValueCodec[math.Int] -} - -func (v balanceCompatValueCodec) Decode(b []byte) (math.Int, error) { - i, err := v.ValueCodec.Decode(b) - if err == nil { - return i, nil - } +// BalanceValueCodec is a codec for encoding bank balances in a backwards compatible way. +// Historically, balances were represented as Coin, now they're represented as a simple math.Int +var BalanceValueCodec = collcodec.NewAltValueCodec(sdk.IntValue, func(bytes []byte) (math.Int, error) { c := new(sdk.Coin) - err = c.Unmarshal(b) + err := c.Unmarshal(bytes) if err != nil { return math.Int{}, err } return c.Amount, nil -} +}) diff --git a/x/bank/types/keys_test.go b/x/bank/types/keys_test.go index cf0c01eddd62..fa2c48669b61 100644 --- a/x/bank/types/keys_test.go +++ b/x/bank/types/keys_test.go @@ -12,16 +12,15 @@ import ( ) func TestBalanceValueCodec(t *testing.T) { - c := NewBalanceCompatValueCodec() t.Run("value codec implementation", func(t *testing.T) { - colltest.TestValueCodec(t, c, math.NewInt(100)) + colltest.TestValueCodec(t, BalanceValueCodec, math.NewInt(100)) }) t.Run("legacy coin", func(t *testing.T) { coin := sdk.NewInt64Coin("coin", 1000) b, err := coin.Marshal() require.NoError(t, err) - amt, err := c.Decode(b) + amt, err := BalanceValueCodec.Decode(b) require.NoError(t, err) require.Equal(t, coin.Amount, amt) }) From 7c92085ca3cae312547d5bf0a30fa4c56085fdc6 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Wed, 5 Jul 2023 14:43:58 +0200 Subject: [PATCH 09/12] minor fix --- x/bank/keeper/collections_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/bank/keeper/collections_test.go b/x/bank/keeper/collections_test.go index 413b71506a75..b29bf4c78653 100644 --- a/x/bank/keeper/collections_test.go +++ b/x/bank/keeper/collections_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "cosmossdk.io/math" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttime "github.com/cometbft/cometbft/types/time" "github.com/golang/mock/gomock" @@ -75,7 +76,7 @@ func TestBankStateCompatibility(t *testing.T) { require.Equal(t, pks[0], collections.Join(sdk.AccAddress("test"), "atom")) // assert the index value will be updated to the new format - err = k.Balances.Indexes.Denom.Reference(ctx, collections.Join(sdk.AccAddress("test"), "atom"), sdk.ZeroInt(), nil) + err = k.Balances.Indexes.Denom.Reference(ctx, collections.Join(sdk.AccAddress("test"), "atom"), math.ZeroInt(), nil) require.NoError(t, err) newRawValue, err := storeService.OpenKVStore(ctx).Get(rawKey) From 57cdf71020a0db61d546986a107d61394dfeab18 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Wed, 5 Jul 2023 14:47:49 +0200 Subject: [PATCH 10/12] lint --- x/bank/keeper/collections_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/bank/keeper/collections_test.go b/x/bank/keeper/collections_test.go index b29bf4c78653..15c22c489d06 100644 --- a/x/bank/keeper/collections_test.go +++ b/x/bank/keeper/collections_test.go @@ -3,7 +3,6 @@ package keeper_test import ( "testing" - "cosmossdk.io/math" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttime "github.com/cometbft/cometbft/types/time" "github.com/golang/mock/gomock" @@ -11,6 +10,7 @@ import ( "cosmossdk.io/collections" "cosmossdk.io/log" + "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec/address" From c21f2afce6ec57c6fb47ed0afda3cdb582e675a1 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Wed, 5 Jul 2023 14:54:05 +0200 Subject: [PATCH 11/12] chore: CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f1c21bf9ce..ac7eb03a17de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -341,6 +341,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/crypto) [#15258](https://github.com/cosmos/cosmos-sdk/pull/15258) Write keyhash file with permissions 0600 instead of 0555. * (cli) [#16138](https://github.com/cosmos/cosmos-sdk/pull/16138) Fix snapshot commands panic if snapshot don't exists. * (x/gov) [#16230](https://github.com/cosmos/cosmos-sdk/pull/16231) Fix: rawlog JSON formatting of proposal_vote option field +* (x/bank) [#16841](https://github.com/cosmos/cosmos-sdk/pull/16841) correctly process legacy `DenomAddressIndex` values. ### Deprecated From 3210115a56d290c37f29359d3ebcba8ec14d92d6 Mon Sep 17 00:00:00 2001 From: unknown unknown Date: Wed, 5 Jul 2023 22:46:13 +0200 Subject: [PATCH 12/12] fix CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7eb03a17de..8378106b97a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes * (server) [#16827](https://github.com/cosmos/cosmos-sdk/pull/16827) Properly use `--trace` flag (before it was setting the trace level instead of displaying the stacktraces). +* (x/bank) [#16841](https://github.com/cosmos/cosmos-sdk/pull/16841) correctly process legacy `DenomAddressIndex` values. ### API Breaking Changes @@ -341,7 +342,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/crypto) [#15258](https://github.com/cosmos/cosmos-sdk/pull/15258) Write keyhash file with permissions 0600 instead of 0555. * (cli) [#16138](https://github.com/cosmos/cosmos-sdk/pull/16138) Fix snapshot commands panic if snapshot don't exists. * (x/gov) [#16230](https://github.com/cosmos/cosmos-sdk/pull/16231) Fix: rawlog JSON formatting of proposal_vote option field -* (x/bank) [#16841](https://github.com/cosmos/cosmos-sdk/pull/16841) correctly process legacy `DenomAddressIndex` values. ### Deprecated