diff --git a/baseapp/block_gas_test.go b/baseapp/block_gas_test.go index c1f4ff2f5c63..9c2ddd1c3c2b 100644 --- a/baseapp/block_gas_test.go +++ b/baseapp/block_gas_test.go @@ -173,7 +173,7 @@ func TestBaseApp_BlockGas(t *testing.T) { require.Equal(t, []byte("ok"), okValue) } // check block gas is always consumed - baseGas := uint64(51732) // baseGas is the gas consumed before tx msg + baseGas := uint64(50702) // baseGas is the gas consumed before tx msg expGasConsumed := addUint64Saturating(tc.gasToConsume, baseGas) if expGasConsumed > txtypes.MaxGasWanted { // capped by gasLimit diff --git a/client/rpc/rpc_test.go b/client/rpc/rpc_test.go index c8911f1a6d28..166519ed1b52 100644 --- a/client/rpc/rpc_test.go +++ b/client/rpc/rpc_test.go @@ -3,6 +3,7 @@ package rpc_test import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/types/address" "strconv" "testing" @@ -112,7 +113,7 @@ func (s *IntegrationTestSuite) TestQueryABCIHeight() { req := abci.RequestQuery{ Path: fmt.Sprintf("store/%s/key", banktypes.StoreKey), Height: tc.reqHeight, - Data: banktypes.CreateAccountBalancesPrefix(val.Address), + Data: address.MustLengthPrefix(val.Address), Prove: true, } diff --git a/fuzz/tests/x_bank_types_addressfrombalancesstore_test.go b/fuzz/tests/x_bank_types_addressfrombalancesstore_test.go deleted file mode 100644 index 1c2235bcd69d..000000000000 --- a/fuzz/tests/x_bank_types_addressfrombalancesstore_test.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build gofuzz || go1.18 - -package tests - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -func FuzzXBankTypesAddressFromBalancesStore(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { - types.AddressAndDenomFromBalancesStore(data) - }) -} diff --git a/go.mod b/go.mod index a988b423c8b4..7fdb5bb7fc90 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/cosmos/cosmos-sdk require ( cosmossdk.io/api v0.4.0 - cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba + cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 cosmossdk.io/core v0.6.1 cosmossdk.io/depinject v1.0.0-alpha.3 cosmossdk.io/errors v1.0.0-beta.7 diff --git a/go.sum b/go.sum index af9f6a33b6f5..ec1cca789d8f 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.0 h1:x90DmdidP6EhzktAa/6/IofSHidDnPjahdlrUvyQZQw= cosmossdk.io/api v0.4.0/go.mod h1:TWDzBhUBhI1LhSf2XSYpfIBf6D4mbLu/fvzvDfhcaYM= -cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba h1:S4PYij/tX3Op/hwenVEN9D+M27JRcwSwVqE3UA0BnwM= -cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba/go.mod h1:lpS+G8bGC2anqzWdndTzjnQnuMO/qAcgZUkGJp4i3rc= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 h1:QQZ0Qz8Gy/EmUNMRiHkUPG3BMA6OqEBp67IsfKETXIU= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4/go.mod h1:/vS4ugR7ad3IciUd5TQuP2Ldz3NukHK2u/l5xTxXbbE= 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= diff --git a/simapp/go.mod b/simapp/go.mod index 5dc8cbb0015e..8ca62228e039 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -36,7 +36,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.0.0-20230309163709-87da587416ba // indirect + cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 // indirect cosmossdk.io/errors v1.0.0-beta.7 // indirect cosmossdk.io/x/tx v0.5.1-0.20230407182919-057d2e09bd63 // indirect filippo.io/edwards25519 v1.0.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 2b4629c5fa6f..40e2f0bf0aed 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/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.0.0-20230309163709-87da587416ba h1:S4PYij/tX3Op/hwenVEN9D+M27JRcwSwVqE3UA0BnwM= -cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba/go.mod h1:lpS+G8bGC2anqzWdndTzjnQnuMO/qAcgZUkGJp4i3rc= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 h1:QQZ0Qz8Gy/EmUNMRiHkUPG3BMA6OqEBp67IsfKETXIU= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4/go.mod h1:/vS4ugR7ad3IciUd5TQuP2Ldz3NukHK2u/l5xTxXbbE= 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= diff --git a/tests/go.mod b/tests/go.mod index 004ecdff8724..910c43388054 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -37,7 +37,7 @@ require ( cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/storage v1.30.0 // indirect cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba // indirect - cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba // indirect + cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 // indirect cosmossdk.io/core v0.6.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect diff --git a/tests/go.sum b/tests/go.sum index 2ae717da0fcf..8a8905064a81 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/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.0.0-20230309163709-87da587416ba h1:S4PYij/tX3Op/hwenVEN9D+M27JRcwSwVqE3UA0BnwM= -cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba/go.mod h1:lpS+G8bGC2anqzWdndTzjnQnuMO/qAcgZUkGJp4i3rc= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4 h1:QQZ0Qz8Gy/EmUNMRiHkUPG3BMA6OqEBp67IsfKETXIU= +cosmossdk.io/collections v0.0.0-20230411101845-3d1a0b8840e4/go.mod h1:/vS4ugR7ad3IciUd5TQuP2Ldz3NukHK2u/l5xTxXbbE= 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= diff --git a/tests/integration/bank/keeper/deterministic_test.go b/tests/integration/bank/keeper/deterministic_test.go index 394727f776d6..2acef84bad15 100644 --- a/tests/integration/bank/keeper/deterministic_test.go +++ b/tests/integration/bank/keeper/deterministic_test.go @@ -478,5 +478,5 @@ func TestGRPCDenomOwners(t *testing.T) { req := &banktypes.QueryDenomOwnersRequest{ Denom: coin1.GetDenom(), } - testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.DenomOwners, 2525, false) + testdata.DeterministicIterations(f.ctx, t, req, f.queryClient.DenomOwners, 2516, false) } diff --git a/testutil/context.go b/testutil/context.go index 3be3b2d9a4d8..f2a9bdaf922f 100644 --- a/testutil/context.go +++ b/testutil/context.go @@ -37,7 +37,7 @@ type TestContext struct { CMS store.CommitMultiStore } -func DefaultContextWithDB(t *testing.T, key, tkey storetypes.StoreKey) TestContext { +func DefaultContextWithDB(t testing.TB, key, tkey storetypes.StoreKey) TestContext { db := dbm.NewMemDB() cms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) diff --git a/types/collections.go b/types/collections.go index bda34cd0c82d..f5e3f8a3668b 100644 --- a/types/collections.go +++ b/types/collections.go @@ -85,6 +85,40 @@ func (a genericAddressKey[T]) SizeNonTerminal(key T) int { return collections.BytesKey.SizeNonTerminal(key) } +// Deprecated: genericAddressIndexKey is a special key codec used to retain state backwards compatibility +// when a generic address key (be: AccAddress, ValAddress, ConsAddress), is used as an index key. +// More docs can be found in the AddressKeyAsIndexKey function. +type genericAddressIndexKey[T addressUnion] struct { + collcodec.KeyCodec[T] +} + +func (g genericAddressIndexKey[T]) Encode(buffer []byte, key T) (int, error) { + return g.EncodeNonTerminal(buffer, key) +} + +func (g genericAddressIndexKey[T]) Decode(buffer []byte) (int, T, error) { + return g.DecodeNonTerminal(buffer) +} + +func (g genericAddressIndexKey[T]) Size(key T) int { return g.SizeNonTerminal(key) } + +func (g genericAddressIndexKey[T]) KeyType() string { return "index_key/" + g.KeyCodec.KeyType() } + +// Deprecated: AddressKeyAsIndexKey implements an SDK backwards compatible indexing key encoder +// for addresses. +// The status quo in the SDK is that address keys are length prefixed even when they're the +// last part of a composite key. This should never be used unless to retain state compatibility. +// For example, a composite key composed of `[string, address]` in theory would need you only to +// define a way to understand when the string part finishes, we usually do this by appending a null +// byte to the string, then when you know when the string part finishes, it's logical that the +// part which remains is the address key. In the SDK instead we prepend to the address key its +// length too. +func AddressKeyAsIndexKey[T addressUnion](keyCodec collcodec.KeyCodec[T]) collcodec.KeyCodec[T] { + return genericAddressIndexKey[T]{ + keyCodec, + } +} + // Collection Codecs type intValueCodec struct{} diff --git a/types/collections_test.go b/types/collections_test.go index fd5ea9a0244b..04e612fa2be4 100644 --- a/types/collections_test.go +++ b/types/collections_test.go @@ -18,4 +18,8 @@ func TestCollectionsCorrectness(t *testing.T) { t.Run("ConsAddress", func(t *testing.T) { colltest.TestKeyCodec(t, ConsAddressKey, ConsAddress{0x32, 0x0, 0x0, 0x3}) }) + + t.Run("AddressIndexingKey", func(t *testing.T) { + colltest.TestKeyCodec(t, AddressKeyAsIndexKey(AccAddressKey), AccAddress{0x2, 0x5, 0x8}) + }) } diff --git a/types/query/collections_pagination.go b/types/query/collections_pagination.go index ae1819332069..88c7e4e267ef 100644 --- a/types/query/collections_pagination.go +++ b/types/query/collections_pagination.go @@ -2,14 +2,22 @@ package query import ( "context" - "errors" - "fmt" - "cosmossdk.io/collections" collcodec "cosmossdk.io/collections/codec" storetypes "cosmossdk.io/store/types" + "errors" + "fmt" ) +// WithCollectionPaginationPairPrefix applies a prefix to a collection, whose key is a collection.Pair, +// being paginated that needs prefixing. +func WithCollectionPaginationPairPrefix[K1, K2 any](prefix K1) func(o *CollectionsPaginateOptions[collections.Pair[K1, K2]]) { + return func(o *CollectionsPaginateOptions[collections.Pair[K1, K2]]) { + prefix := collections.PairPrefix[K1, K2](prefix) + o.Prefix = &prefix + } +} + // CollectionsPaginateOptions provides extra options for pagination in collections. type CollectionsPaginateOptions[K any] struct { // Prefix allows to optionally set a prefix for the pagination. @@ -41,7 +49,7 @@ func CollectionFilteredPaginate[K, V any, C Collection[K, V]]( ctx context.Context, coll C, pageReq *PageRequest, - predicateFunc func(key K, value V) (include bool), + predicateFunc func(key K, value V) (include bool, err error), opts ...func(opt *CollectionsPaginateOptions[K]), ) ([]collections.KeyValue[K, V], *PageResponse, error) { if pageReq == nil { @@ -89,7 +97,7 @@ func CollectionFilteredPaginate[K, V any, C Collection[K, V]]( } // invalid iter error is ignored to retain Paginate behavior if errors.Is(err, collections.ErrInvalidIterator) { - return results, pageRes, nil + return results, new(PageResponse), nil } // strip the prefix from next key if len(pageRes.NextKey) != 0 && prefix != nil { @@ -108,7 +116,7 @@ func collFilteredPaginateNoKey[K, V any, C Collection[K, V]]( offset uint64, limit uint64, countTotal bool, - predicateFunc func(K, V) bool, + predicateFunc func(K, V) (bool, error), ) ([]collections.KeyValue[K, V], *PageResponse, error) { iterator, err := getCollIter[K, V](ctx, coll, prefix, nil, reverse) if err != nil { @@ -137,12 +145,17 @@ func collFilteredPaginateNoKey[K, V any, C Collection[K, V]]( // if no predicate function is specified then we just include the result if predicateFunc == nil { results = append(results, kv) - count++ // if predicate function is defined we check if the result matches the filtering criteria - } else if predicateFunc(kv.Key, kv.Value) { - results = append(results, kv) - count++ + } else { + include, err := predicateFunc(kv.Key, kv.Value) + if err != nil { + return nil, nil, err + } + if include { + results = append(results, kv) + } } + count++ // second case, we found all the objects specified within the limit case count == limit: key, err := iterator.Key() @@ -200,7 +213,7 @@ func collFilteredPaginateByKey[K, V any, C Collection[K, V]]( key []byte, reverse bool, limit uint64, - predicateFunc func(K, V) bool, + predicateFunc func(K, V) (bool, error), ) ([]collections.KeyValue[K, V], *PageResponse, error) { iterator, err := getCollIter[K, V](ctx, coll, prefix, key, reverse) if err != nil { @@ -237,13 +250,18 @@ func collFilteredPaginateByKey[K, V any, C Collection[K, V]]( // if no predicate is specified then we just append the result if predicateFunc == nil { results = append(results, kv) - count++ // if predicate is applied we execute the predicate function // and append only if predicateFunc yields true. - } else if predicateFunc(kv.Key, kv.Value) { - results = append(results, kv) - count++ + } else { + include, err := predicateFunc(kv.Key, kv.Value) + if err != nil { + return nil, nil, err + } + if include { + results = append(results, kv) + } } + count++ } return results, &PageResponse{ @@ -258,14 +276,20 @@ func encodeCollKey[K, V any, C Collection[K, V]](coll C, key K) ([]byte, error) return buffer, err } -func getCollIter[K, V any, C Collection[K, V]](ctx context.Context, coll C, prefix, start []byte, reverse bool) (collections.Iterator[K, V], error) { +func getCollIter[K, V any, C Collection[K, V]](ctx context.Context, coll C, prefix []byte, start []byte, reverse bool) (collections.Iterator[K, V], error) { + // TODO: maybe can be simplified + if reverse { + var end []byte + if prefix != nil { + start = storetypes.PrefixEndBytes(append(prefix, start...)) + end = prefix + } + return coll.IterateRaw(ctx, end, start, collections.OrderDescending) + } var end []byte if prefix != nil { start = append(prefix, start...) end = storetypes.PrefixEndBytes(prefix) } - if reverse { - return coll.IterateRaw(ctx, nil, start, collections.OrderDescending) - } return coll.IterateRaw(ctx, start, end, collections.OrderAscending) } diff --git a/types/query/collections_pagination_test.go b/types/query/collections_pagination_test.go index cf38540e9d36..ebce41a4f22e 100644 --- a/types/query/collections_pagination_test.go +++ b/types/query/collections_pagination_test.go @@ -48,7 +48,7 @@ func TestCollectionPagination(t *testing.T) { type test struct { req *PageRequest expResp *PageResponse - filter func(key, value uint64) bool + filter func(key, value uint64) (bool, error) expResults []collections.KeyValue[uint64, uint64] wantErr error } @@ -99,15 +99,14 @@ func TestCollectionPagination(t *testing.T) { Limit: 3, }, expResp: &PageResponse{ - NextKey: encodeKey(5), + NextKey: encodeKey(3), }, - filter: func(key, value uint64) bool { - return key%2 == 0 + filter: func(key, value uint64) (bool, error) { + return key%2 == 0, nil }, expResults: []collections.KeyValue[uint64, uint64]{ {Key: 0, Value: 0}, {Key: 2, Value: 2}, - {Key: 4, Value: 4}, }, }, "filtered with key": { @@ -116,15 +115,14 @@ func TestCollectionPagination(t *testing.T) { Limit: 3, }, expResp: &PageResponse{ - NextKey: encodeKey(7), + NextKey: encodeKey(5), }, - filter: func(key, value uint64) bool { - return key%2 == 0 + filter: func(key, value uint64) (bool, error) { + return key%2 == 0, nil }, expResults: []collections.KeyValue[uint64, uint64]{ {Key: 2, Value: 2}, {Key: 4, Value: 4}, - {Key: 6, Value: 6}, }, }, } diff --git a/types/query/pagination_test.go b/types/query/pagination_test.go index bfb745586596..a31c43c0e3e9 100644 --- a/types/query/pagination_test.go +++ b/types/query/pagination_test.go @@ -231,7 +231,7 @@ func (s *paginationTestSuite) TestReversePagination() { request := types.NewQueryAllBalancesRequest(addr1, pageReq, false) res1, err := queryClient.AllBalances(gocontext.Background(), request) s.Require().NoError(err) - s.Require().Equal(res1.Balances.Len(), 2) + s.Require().Equal(2, res1.Balances.Len()) s.Require().NotNil(res1.Pagination.NextKey) s.T().Log("verify paginate with custom limit and countTotal, Reverse false") diff --git a/x/bank/keeper/genesis.go b/x/bank/keeper/genesis.go index e214570e5035..73099439a8ec 100644 --- a/x/bank/keeper/genesis.go +++ b/x/bank/keeper/genesis.go @@ -1,6 +1,7 @@ package keeper import ( + "cosmossdk.io/collections" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,8 +25,11 @@ func (k BaseKeeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { for _, balance := range genState.Balances { addr := balance.GetAddress() - if err := k.initBalances(ctx, addr, balance.Coins); err != nil { - panic(fmt.Errorf("error on setting balances %w", err)) + for _, coin := range balance.Coins { + err := k.Balances.Set(ctx, collections.Join(addr, coin.Denom), coin.Amount) + if err != nil { + panic(err) + } } totalSupply = totalSupply.Add(balance.Coins...) diff --git a/x/bank/keeper/grpc_query.go b/x/bank/keeper/grpc_query.go index c6e3b6437ed6..53a9a8a3a2b2 100644 --- a/x/bank/keeper/grpc_query.go +++ b/x/bank/keeper/grpc_query.go @@ -2,7 +2,7 @@ package keeper import ( "context" - + "cosmossdk.io/collections" "cosmossdk.io/math" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -51,26 +51,17 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances sdkCtx := sdk.UnwrapSDKContext(ctx) balances := sdk.NewCoins() - accountStore := k.getAccountStore(sdkCtx, addr) - - pageRes, err := query.Paginate(accountStore, req.Pagination, func(key, value []byte) error { - denom := string(key) - // IBC denom metadata will be registered in ibc-go after first mint - // - // Since: ibc-go v7 + _, pageRes, err := query.CollectionFilteredPaginate(ctx, k.Balances, req.Pagination, func(key collections.Pair[sdk.AccAddress, string], value math.Int) (include bool, err error) { + denom := key.K2() if req.ResolveDenom { if metadata, ok := k.GetDenomMetaData(sdkCtx, denom); ok { denom = metadata.Display } } - balance, err := UnmarshalBalanceCompat(k.cdc, value, denom) - if err != nil { - return err - } - balances = append(balances, balance) - return nil - }) + balances = append(balances, sdk.NewCoin(denom, value)) + return false, nil // we don't include results because we're appending them here. + }, query.WithCollectionPaginationPairPrefix[sdk.AccAddress, string](addr)) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err) } @@ -93,13 +84,13 @@ func (k BaseKeeper) SpendableBalances(ctx context.Context, req *types.QuerySpend sdkCtx := sdk.UnwrapSDKContext(ctx) balances := sdk.NewCoins() - accountStore := k.getAccountStore(sdkCtx, addr) zeroAmt := math.ZeroInt() - pageRes, err := query.Paginate(accountStore, req.Pagination, func(key, _ []byte) error { - balances = append(balances, sdk.NewCoin(string(key), zeroAmt)) - return nil - }) + _, pageRes, err := query.CollectionFilteredPaginate(ctx, k.Balances, req.Pagination, func(key collections.Pair[sdk.AccAddress, string], _ math.Int) (include bool, err error) { + balances = append(balances, sdk.NewCoin(key.K2(), zeroAmt)) + return false, nil // not including results as they're appended here + }, query.WithCollectionPaginationPairPrefix[sdk.AccAddress, string](addr)) + if err != nil { return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err) } @@ -237,34 +228,24 @@ func (k BaseKeeper) DenomOwners( return nil, status.Error(codes.InvalidArgument, err.Error()) } - ctx := sdk.UnwrapSDKContext(goCtx) - denomPrefixStore := k.getDenomAddressPrefixStore(ctx, req.Denom) - var denomOwners []*types.DenomOwner - pageRes, err := query.FilteredPaginate( - denomPrefixStore, - req.Pagination, - func(key, _ []byte, accumulate bool) (bool, error) { - if accumulate { - address, _, err := types.AddressAndDenomFromBalancesStore(key) - if err != nil { - return false, err - } - - denomOwners = append( - denomOwners, - &types.DenomOwner{ - Address: address.String(), - Balance: k.GetBalance(ctx, address, req.Denom), - }, - ) - } - return true, nil + _, pageRes, err := query.CollectionFilteredPaginate(goCtx, k.Balances.Indexes.Denom, req.Pagination, + func(key collections.Pair[string, sdk.AccAddress], value collections.NoValue) (include bool, err error) { + amt, err := k.Balances.Get(goCtx, collections.Join(key.K2(), req.Denom)) + if err != nil { + return false, err + } + denomOwners = append(denomOwners, &types.DenomOwner{ + Address: key.K2().String(), + Balance: sdk.NewCoin(req.Denom, amt), + }) + return false, nil }, + query.WithCollectionPaginationPairPrefix[string, sdk.AccAddress](req.Denom), ) if err != nil { - return nil, status.Error(codes.Internal, err.Error()) + return nil, err } return &types.QueryDenomOwnersResponse{DenomOwners: denomOwners, Pagination: pageRes}, nil diff --git a/x/bank/keeper/grpc_query_test.go b/x/bank/keeper/grpc_query_test.go index 82314501d664..c76850c41834 100644 --- a/x/bank/keeper/grpc_query_test.go +++ b/x/bank/keeper/grpc_query_test.go @@ -461,7 +461,7 @@ func (suite *KeeperTestSuite) TestGRPCDenomOwners() { suite.Require().NoError(keeper.MintCoins(ctx, minttypes.ModuleName, initCoins)) for i := 0; i < 10; i++ { - addr := sdk.AccAddress([]byte(fmt.Sprintf("account-%d", i))) + addr := sdk.AccAddress(fmt.Sprintf("account-%d", i)) bal := sdk.NewCoins(sdk.NewCoin( sdk.DefaultBondDenom, diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 153ee65c7c44..7018a00ac13d 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -1,16 +1,15 @@ package keeper import ( + "cosmossdk.io/collections" "fmt" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/bank/types" ) @@ -277,74 +276,21 @@ func (k BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.C return nil } -// initBalances sets the balance (multiple coins) for an account by address. -// An error is returned upon failure. -func (k BaseSendKeeper) initBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error { - accountStore := k.getAccountStore(ctx, addr) - denomPrefixStores := make(map[string]prefix.Store) // memoize prefix stores - - for i := range balances { - balance := balances[i] - if !balance.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, balance.String()) - } - - // x/bank invariants prohibit persistence of zero balances - if !balance.IsZero() { - amount, err := balance.Amount.Marshal() - if err != nil { - return err - } - accountStore.Set([]byte(balance.Denom), amount) - - denomPrefixStore, ok := denomPrefixStores[balance.Denom] - if !ok { - denomPrefixStore = k.getDenomAddressPrefixStore(ctx, balance.Denom) - denomPrefixStores[balance.Denom] = denomPrefixStore - } - - // Store a reverse index from denomination to account address with a - // sentinel value. - denomAddrKey := address.MustLengthPrefix(addr) - if !denomPrefixStore.Has(denomAddrKey) { - denomPrefixStore.Set(denomAddrKey, []byte{0}) - } - } - } - - return nil -} - // setBalance sets the coin balance for an account by address. func (k BaseSendKeeper) setBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error { if !balance.IsValid() { return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, balance.String()) } - accountStore := k.getAccountStore(ctx, addr) - denomPrefixStore := k.getDenomAddressPrefixStore(ctx, balance.Denom) - // x/bank invariants prohibit persistence of zero balances if balance.IsZero() { - accountStore.Delete([]byte(balance.Denom)) - denomPrefixStore.Delete(address.MustLengthPrefix(addr)) - } else { - amount, err := balance.Amount.Marshal() + err := k.Balances.Remove(ctx, collections.Join(addr, balance.Denom)) if err != nil { return err } - - accountStore.Set([]byte(balance.Denom), amount) - - // Store a reverse index from denomination to account address with a - // sentinel value. - denomAddrKey := address.MustLengthPrefix(addr) - if !denomPrefixStore.Has(denomAddrKey) { - denomPrefixStore.Set(denomAddrKey, []byte{0}) - } + return nil } - - return nil + return k.Balances.Set(ctx, collections.Join(addr, balance.Denom), balance.Amount) } // IsSendEnabledCoins checks the coins provided and returns an ErrSendDisabled diff --git a/x/bank/keeper/view.go b/x/bank/keeper/view.go index 8164b4824e5d..c56d8c39d5ba 100644 --- a/x/bank/keeper/view.go +++ b/x/bank/keeper/view.go @@ -1,7 +1,9 @@ package keeper import ( + "cosmossdk.io/collections/indexes" "fmt" + "github.com/cockroachdb/errors" "cosmossdk.io/collections" @@ -11,7 +13,6 @@ import ( "cosmossdk.io/math" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" @@ -39,6 +40,23 @@ type ViewKeeper interface { IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) } +func newBalancesIndexes(sb *collections.SchemaBuilder) BalancesIndexes { + return BalancesIndexes{ + Denom: indexes.NewReversePair[math.Int]( + sb, types.DenomAddressPrefix, "address_by_denom_index", + collections.PairKeyCodec(sdk.AddressKeyAsIndexKey(sdk.AccAddressKey), collections.StringKey), // NOTE: refer to the AddressKeyAsIndexKey docs to understand why we do this. + ), + } +} + +type BalancesIndexes struct { + Denom *indexes.ReversePair[sdk.AccAddress, string, math.Int] +} + +func (b BalancesIndexes) IndexesList() []collections.Index[collections.Pair[sdk.AccAddress, string], math.Int] { + return []collections.Index[collections.Pair[sdk.AccAddress, string], math.Int]{b.Denom} +} + // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. type BaseViewKeeper struct { cdc codec.BinaryCodec @@ -49,6 +67,7 @@ type BaseViewKeeper struct { Supply collections.Map[string, math.Int] DenomMetadata collections.Map[string, types.Metadata] SendEnabled collections.Map[string, bool] + Balances *collections.IndexedMap[collections.Pair[sdk.AccAddress, string], math.Int, BalancesIndexes] Params collections.Item[types.Params] } @@ -62,6 +81,7 @@ func NewBaseViewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ak t 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)), Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), } @@ -123,35 +143,22 @@ func (k BaseViewKeeper) GetAccountsBalances(ctx sdk.Context) []types.Balance { // GetBalance returns the balance of a specific denomination for a given account // by address. func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { - accountStore := k.getAccountStore(ctx, addr) - bz := accountStore.Get([]byte(denom)) - balance, err := UnmarshalBalanceCompat(k.cdc, bz, denom) + amt, err := k.Balances.Get(ctx, collections.Join(addr, denom)) if err != nil { - panic(err) + return sdk.NewCoin(denom, sdk.ZeroInt()) } - - return balance + return sdk.NewCoin(denom, amt) } // IterateAccountBalances iterates over the balances of a single account and // provides the token balance to a callback. If true is returned from the // callback, iteration is halted. func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) { - accountStore := k.getAccountStore(ctx, addr) - - iterator := accountStore.Iterator(nil, nil) - defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() }) - - for ; iterator.Valid(); iterator.Next() { - denom := string(iterator.Key()) - balance, err := UnmarshalBalanceCompat(k.cdc, iterator.Value(), denom) - if err != nil { - panic(err) - } - - if cb(balance) { - break - } + err := k.Balances.Walk(ctx, collections.NewPrefixedPairRange[sdk.AccAddress, string](addr), func(key collections.Pair[sdk.AccAddress, string], value math.Int) bool { + return cb(sdk.NewCoin(key.K2(), value)) + }) + if err != nil && !errors.Is(err, collections.ErrInvalidIterator) { // TODO(tip): is this the correct strategy + panic(err) } } @@ -159,29 +166,11 @@ func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddr // denominations that are provided to a callback. If true is returned from the // callback, iteration is halted. func (k BaseViewKeeper) IterateAllBalances(ctx sdk.Context, cb func(sdk.AccAddress, sdk.Coin) bool) { - store := ctx.KVStore(k.storeKey) - balancesStore := prefix.NewStore(store, types.BalancesPrefix) - - iterator := balancesStore.Iterator(nil, nil) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - address, denom, err := types.AddressAndDenomFromBalancesStore(iterator.Key()) - if err != nil { - k.Logger(ctx).With("key", iterator.Key(), "err", err).Error("failed to get address from balances store") - // TODO: revisit, for now, panic here to keep same behavior as in 0.42 - // ref: https://github.com/cosmos/cosmos-sdk/issues/7409 - panic(err) - } - - balance, err := UnmarshalBalanceCompat(k.cdc, iterator.Value(), denom) - if err != nil { - panic(err) - } - - if cb(address, balance) { - break - } + err := k.Balances.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, string], value math.Int) bool { + return cb(key.K1(), sdk.NewCoin(key.K2(), value)) + }) + if err != nil { + panic(err) } } @@ -261,39 +250,3 @@ func (k BaseViewKeeper) ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) er return nil } - -// getAccountStore gets the account store of the given address. -func (k BaseViewKeeper) getAccountStore(ctx sdk.Context, addr sdk.AccAddress) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr)) -} - -// getDenomAddressPrefixStore returns a prefix store that acts as a reverse index -// between a denomination and account balance for that denomination. -func (k BaseViewKeeper) getDenomAddressPrefixStore(ctx sdk.Context, denom string) prefix.Store { - return prefix.NewStore(ctx.KVStore(k.storeKey), types.CreateDenomAddressPrefix(denom)) -} - -// UnmarshalBalanceCompat unmarshal balance amount from storage, it's backward-compatible with the legacy format. -func UnmarshalBalanceCompat(cdc codec.BinaryCodec, bz []byte, denom string) (sdk.Coin, error) { - if err := sdk.ValidateDenom(denom); err != nil { - return sdk.Coin{}, err - } - - amount := math.ZeroInt() - if bz == nil { - return sdk.NewCoin(denom, amount), nil - } - - if err := amount.Unmarshal(bz); err != nil { - // try to unmarshal with the legacy format. - var balance sdk.Coin - if cdc.Unmarshal(bz, &balance) != nil { - // return with the original error - return sdk.Coin{}, err - } - return balance, nil - } - - return sdk.NewCoin(denom, amount), nil -} diff --git a/x/bank/migrations/v2/store.go b/x/bank/migrations/v2/store.go index e044e6a47308..6478f139b7f5 100644 --- a/x/bank/migrations/v2/store.go +++ b/x/bank/migrations/v2/store.go @@ -65,7 +65,7 @@ func migrateBalanceKeys(store storetypes.KVStore, logger log.Logger) { for ; oldStoreIter.Valid(); oldStoreIter.Next() { addr := v1.AddressFromBalancesStore(oldStoreIter.Key()) denom := oldStoreIter.Key()[v1auth.AddrLen:] - newStoreKey := types.CreatePrefixedAccountStoreKey(addr, denom) + newStoreKey := CreatePrefixedAccountStoreKey(addr, denom) // Set new key on store. Values don't change. store.Set(newStoreKey, oldStoreIter.Value()) @@ -133,3 +133,9 @@ func pruneZeroSupply(store storetypes.KVStore) error { return nil } + +// CreatePrefixedAccountStoreKey returns the key for the given account and denomination. +// This method can be used when performing an ABCI query for the balance of an account. +func CreatePrefixedAccountStoreKey(addr []byte, denom []byte) []byte { + return append(CreateAccountBalancesPrefix(addr), denom...) +} diff --git a/x/bank/migrations/v2/store_test.go b/x/bank/migrations/v2/store_test.go index 218e40d8b716..5d98b22db48a 100644 --- a/x/bank/migrations/v2/store_test.go +++ b/x/bank/migrations/v2/store_test.go @@ -94,13 +94,13 @@ func TestBalanceKeysMigration(t *testing.T) { err = v2bank.MigrateStore(ctx, bankKey, encCfg.Codec) require.NoError(t, err) - newKey := types.CreatePrefixedAccountStoreKey(addr, []byte(fooCoin.Denom)) + newKey := v2bank.CreatePrefixedAccountStoreKey(addr, []byte(fooCoin.Denom)) // -7 because we replaced "balances" with 0x02, // +1 because we added length-prefix to address. require.Equal(t, len(oldFooKey)-7+1, len(newKey)) require.Nil(t, store.Get(oldFooKey)) require.Equal(t, fooBz, store.Get(newKey)) - newKeyFooBar := types.CreatePrefixedAccountStoreKey(addr, []byte(fooBarCoin.Denom)) + newKeyFooBar := v2bank.CreatePrefixedAccountStoreKey(addr, []byte(fooBarCoin.Denom)) require.Nil(t, store.Get(newKeyFooBar)) // after migration zero balances pruned from store. } diff --git a/x/bank/migrations/v3/store.go b/x/bank/migrations/v3/store.go index d9c41f34617e..45e3d0994415 100644 --- a/x/bank/migrations/v3/store.go +++ b/x/bank/migrations/v3/store.go @@ -57,7 +57,7 @@ func addDenomReverseIndex(store storetypes.KVStore, cdc codec.BinaryCodec, logge return err } - newStore := prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr)) + newStore := prefix.NewStore(store, CreateAccountBalancesPrefix(addr)) newStore.Set([]byte(coin.Denom), bz) denomPrefixStore, ok := denomPrefixStores[balance.Denom] @@ -94,3 +94,8 @@ func migrateDenomMetadata(store storetypes.KVStore, logger log.Logger) error { return nil } + +// CreateAccountBalancesPrefix creates the prefix for an account's balances. +func CreateAccountBalancesPrefix(addr []byte) []byte { + return append(types.BalancesPrefix.Bytes(), address.MustLengthPrefix(addr)...) +} diff --git a/x/bank/migrations/v3/store_test.go b/x/bank/migrations/v3/store_test.go index 33a304f4fcd8..c0e853a2aa3b 100644 --- a/x/bank/migrations/v3/store_test.go +++ b/x/bank/migrations/v3/store_test.go @@ -42,7 +42,7 @@ func TestMigrateStore(t *testing.T) { require.NoError(t, v3.MigrateStore(ctx, bankKey, encCfg.Codec)) for _, b := range balances { - addrPrefixStore := prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr)) + addrPrefixStore := prefix.NewStore(store, v3.CreateAccountBalancesPrefix(addr)) bz := addrPrefixStore.Get([]byte(b.Denom)) var expected math.Int require.NoError(t, expected.Unmarshal(bz)) diff --git a/x/bank/types/keys.go b/x/bank/types/keys.go index 1340d437ac11..33c14f5c3892 100644 --- a/x/bank/types/keys.go +++ b/x/bank/types/keys.go @@ -2,9 +2,9 @@ package types import ( "cosmossdk.io/collections" + collcodec "cosmossdk.io/collections/codec" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - "github.com/cosmos/cosmos-sdk/types/kv" ) const ( @@ -22,12 +22,10 @@ const ( var ( SupplyKey = collections.NewPrefix(0) DenomMetadataPrefix = collections.NewPrefix(1) - DenomAddressPrefix = []byte{0x03} - // BalancesPrefix is the prefix for the account balances store. We use a byte // (instead of `[]byte("balances")` to save some disk space). - BalancesPrefix = []byte{0x02} - + BalancesPrefix = collections.NewPrefix(2) + DenomAddressPrefix = collections.NewPrefix(3) // SendEnabledPrefix is the prefix for the SendDisabled flags for a Denom. SendEnabledPrefix = collections.NewPrefix(4) @@ -35,45 +33,27 @@ var ( ParamsKey = collections.NewPrefix(5) ) -// AddressAndDenomFromBalancesStore returns an account address and denom from a balances prefix -// store. The key must not contain the prefix BalancesPrefix as the prefix store -// iterator discards the actual prefix. -// -// If invalid key is passed, AddressAndDenomFromBalancesStore returns ErrInvalidKey. -func AddressAndDenomFromBalancesStore(key []byte) (sdk.AccAddress, string, error) { - if len(key) == 0 { - return nil, "", ErrInvalidKey - } - - kv.AssertKeyAtLeastLength(key, 1) - - addrBound := int(key[0]) - - if len(key)-1 < addrBound { - return nil, "", ErrInvalidKey +// 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, } - - return key[1 : addrBound+1], string(key[addrBound+1:]), nil -} - -// CreatePrefixedAccountStoreKey returns the key for the given account and denomination. -// This method can be used when performing an ABCI query for the balance of an account. -func CreatePrefixedAccountStoreKey(addr, denom []byte) []byte { - return append(CreateAccountBalancesPrefix(addr), denom...) } -// CreateAccountBalancesPrefix creates the prefix for an account's balances. -func CreateAccountBalancesPrefix(addr []byte) []byte { - return append(BalancesPrefix, address.MustLengthPrefix(addr)...) +type balanceCompatValueCodec struct { + collcodec.ValueCodec[math.Int] } -// CreateDenomAddressPrefix creates a prefix for a reverse index of denomination -// to account balance for that denomination. -func CreateDenomAddressPrefix(denom string) []byte { - // we add a "zero" byte at the end - null byte terminator, to allow prefix denom prefix - // scan. Setting it is not needed (key[last] = 0) - because this is the default. - key := make([]byte, len(DenomAddressPrefix)+len(denom)+1) - copy(key, DenomAddressPrefix) - copy(key[len(DenomAddressPrefix):], denom) - return key +func (v balanceCompatValueCodec) Decode(b []byte) (math.Int, error) { + i, err := v.ValueCodec.Decode(b) + if err == nil { + return i, nil + } + c := new(sdk.Coin) + err = c.Unmarshal(b) + 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 351b54255bea..b00cc0d59f1c 100644 --- a/x/bank/types/keys_test.go +++ b/x/bank/types/keys_test.go @@ -1,70 +1,25 @@ -package types_test +package types import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - + "cosmossdk.io/collections/colltest" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + "testing" ) -func cloneAppend(bz, tail []byte) (res []byte) { - res = make([]byte, len(bz)+len(tail)) - copy(res, bz) - copy(res[len(bz):], tail) - return -} - -func TestAddressFromBalancesStore(t *testing.T) { - addr, err := sdk.AccAddressFromBech32("cosmos1n88uc38xhjgxzw9nwre4ep2c8ga4fjxcar6mn7") - require.NoError(t, err) - addrLen := len(addr) - require.Equal(t, 20, addrLen) - key := cloneAppend(address.MustLengthPrefix(addr), []byte("stake")) - - tests := []struct { - name string - key []byte - wantErr bool - expectedKey sdk.AccAddress - }{ - {"valid", key, false, addr}, - {"#9111", []byte("\xff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), false, nil}, - {"empty", []byte(""), true, nil}, - {"invalid", []byte("3AA"), true, nil}, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - addr, denom, err := types.AddressAndDenomFromBalancesStore(tc.key) - if tc.wantErr { - assert.Error(t, err) - assert.True(t, errors.Is(types.ErrInvalidKey, err)) - } else { - assert.NoError(t, err) - } - if len(tc.expectedKey) > 0 { - assert.Equal(t, tc.expectedKey, addr) - assert.Equal(t, "stake", denom) - } - }) - } -} - -func TestCreateDenomAddressPrefix(t *testing.T) { - require := require.New(t) - - key := types.CreateDenomAddressPrefix("") - require.Len(key, len(types.DenomAddressPrefix)+1) - require.Equal(append(types.DenomAddressPrefix, 0), key) - - key = types.CreateDenomAddressPrefix("abc") - require.Len(key, len(types.DenomAddressPrefix)+4) - require.Equal(append(types.DenomAddressPrefix, 'a', 'b', 'c', 0), key) +func TestBalanceValueCodec(t *testing.T) { + c := NewBalanceCompatValueCodec() + t.Run("value codec implementation", func(t *testing.T) { + colltest.TestValueCodec(t, c, 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) + require.NoError(t, err) + require.Equal(t, coin.Amount, amt) + }) } diff --git a/x/circuit/go.mod b/x/circuit/go.mod index 42be166aebb7..bb7387e483fe 100644 --- a/x/circuit/go.mod +++ b/x/circuit/go.mod @@ -3,7 +3,6 @@ module github.com/cosmos/cosmos-sdk/x/circuit go 1.20 require ( - cosmossdk.io/store v0.1.0-alpha.1.0.20230328185921-37ba88872dbc github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20230330094838-d21f58c638d5 github.com/cosmos/gogoproto v1.4.7 github.com/golang/protobuf v1.5.3 @@ -22,6 +21,7 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 // indirect cosmossdk.io/log v1.0.0 // indirect cosmossdk.io/math v1.0.0 // indirect + cosmossdk.io/store v0.1.0-alpha.1.0.20230328185921-37ba88872dbc // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect github.com/armon/go-metrics v0.4.1 // indirect