refactor(bank): move bank balances to use collections (#15327)
This commit is contained in:
parent
3d1a0b8840
commit
7ab0dfc494
@ -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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
2
go.mod
2
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
|
||||
|
||||
4
go.sum
4
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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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{}
|
||||
|
||||
@ -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})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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),
|
||||
},
|
||||
)
|
||||
_, 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
|
||||
}
|
||||
|
||||
return true, nil
|
||||
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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
@ -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)...)
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
kv.AssertKeyAtLeastLength(key, 1)
|
||||
type balanceCompatValueCodec struct {
|
||||
collcodec.ValueCodec[math.Int]
|
||||
}
|
||||
|
||||
addrBound := int(key[0])
|
||||
|
||||
if len(key)-1 < addrBound {
|
||||
return nil, "", ErrInvalidKey
|
||||
func (v balanceCompatValueCodec) Decode(b []byte) (math.Int, error) {
|
||||
i, err := v.ValueCodec.Decode(b)
|
||||
if err == nil {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
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)...)
|
||||
}
|
||||
|
||||
// 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
|
||||
c := new(sdk.Coin)
|
||||
err = c.Unmarshal(b)
|
||||
if err != nil {
|
||||
return math.Int{}, err
|
||||
}
|
||||
return c.Amount, nil
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user