diff --git a/.changelog/unreleased/features/2498-sanction-module-collections.md b/.changelog/unreleased/features/2498-sanction-module-collections.md new file mode 100644 index 0000000000..4f355bcd72 --- /dev/null +++ b/.changelog/unreleased/features/2498-sanction-module-collections.md @@ -0,0 +1 @@ +* Migrated sanction module to collections for state storage [#2498](https://github.com/provenance-io/provenance/issues/2498). diff --git a/app/app.go b/app/app.go index caa85e474b..fcf9c362dd 100644 --- a/app/app.go +++ b/app/app.go @@ -728,7 +728,7 @@ func New( unsanctionableAddrs = append(unsanctionableAddrs, authtypes.NewModuleAddress(mName)) } unsanctionableAddrs = append(unsanctionableAddrs, authtypes.NewModuleAddress(quarantine.ModuleName)) - app.SanctionKeeper = sanctionkeeper.NewKeeper(appCodec, keys[sanction.StoreKey], + app.SanctionKeeper = sanctionkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[sanction.StoreKey]), app.BankKeeper, &app.GovKeeper, govAuthority, unsanctionableAddrs) diff --git a/x/sanction/codec.go b/x/sanction/codec.go index f6fd87f0cf..6d0cd3f1d3 100644 --- a/x/sanction/codec.go +++ b/x/sanction/codec.go @@ -1,6 +1,12 @@ package sanction import ( + "encoding/binary" + "encoding/json" + fmt "fmt" + + "cosmossdk.io/collections" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" @@ -15,3 +21,256 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } + +// AccAddressKey implements KeyCodec[sdk.AccAddress] for length-prefixed addresses +type AccAddressKey struct{} + +func (a AccAddressKey) Encode(buffer []byte, addr sdk.AccAddress) (int, error) { + size := a.Size(addr) + if len(buffer) < size { + return 0, fmt.Errorf("buffer too small: need %d, got %d", size, len(buffer)) + } + + buffer[0] = byte(len(addr)) + copy(buffer[1:], addr) + return size, nil +} + +func (a AccAddressKey) Decode(buffer []byte) (int, sdk.AccAddress, error) { + if len(buffer) == 0 { + return 0, nil, fmt.Errorf("empty buffer") + } + + addrLen := int(buffer[0]) + size := 1 + addrLen + + if len(buffer) < size { + return 0, nil, fmt.Errorf("insufficient bytes: need %d, got %d", size, len(buffer)) + } + + addr := sdk.AccAddress(buffer[1 : 1+addrLen]) + return size, addr, nil +} + +func (a AccAddressKey) Size(addr sdk.AccAddress) int { + return 1 + len(addr) +} + +func (a AccAddressKey) EncodeJSON(addr sdk.AccAddress) ([]byte, error) { + return json.Marshal(addr.String()) +} + +func (a AccAddressKey) DecodeJSON(b []byte) (sdk.AccAddress, error) { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return nil, err + } + return sdk.AccAddressFromBech32(s) +} + +func (a AccAddressKey) Stringify(addr sdk.AccAddress) string { + return addr.String() +} + +func (a AccAddressKey) KeyType() string { + return "sdk.AccAddress" +} + +func (a AccAddressKey) EncodeNonTerminal(buffer []byte, addr sdk.AccAddress) (int, error) { + return a.Encode(buffer, addr) +} + +func (a AccAddressKey) DecodeNonTerminal(buffer []byte) (int, sdk.AccAddress, error) { + return a.Decode(buffer) +} + +func (a AccAddressKey) SizeNonTerminal(addr sdk.AccAddress) int { + return a.Size(addr) +} + +// TemporaryKeyCodec implements KeyCodec[collections.Pair[sdk.AccAddress, uint64]] +// Format: +type TemporaryKeyCodec struct{} + +func (t TemporaryKeyCodec) Encode(buffer []byte, key collections.Pair[sdk.AccAddress, uint64]) (int, error) { + addr := key.K1() + propID := key.K2() + + size := t.Size(key) + if len(buffer) < size { + return 0, fmt.Errorf("buffer too small: need %d, got %d", size, len(buffer)) + } + + buffer[0] = byte(len(addr)) + copy(buffer[1:], addr) + binary.BigEndian.PutUint64(buffer[1+len(addr):], propID) + + return size, nil +} + +func (t TemporaryKeyCodec) Decode(buffer []byte) (int, collections.Pair[sdk.AccAddress, uint64], error) { + if len(buffer) < 1+8 { + return 0, collections.Pair[sdk.AccAddress, uint64]{}, fmt.Errorf("buffer too short: got %d bytes", len(buffer)) + } + + addrLen := int(buffer[0]) + size := 1 + addrLen + 8 + + if len(buffer) < size { + return 0, collections.Pair[sdk.AccAddress, uint64]{}, fmt.Errorf("invalid address length: need %d, got %d", size, len(buffer)) + } + + addr := sdk.AccAddress(buffer[1 : 1+addrLen]) + propID := binary.BigEndian.Uint64(buffer[1+addrLen:]) + + return size, collections.Join(addr, propID), nil +} + +func (t TemporaryKeyCodec) Size(key collections.Pair[sdk.AccAddress, uint64]) int { + return 1 + len(key.K1()) + 8 +} + +func (t TemporaryKeyCodec) EncodeJSON(key collections.Pair[sdk.AccAddress, uint64]) ([]byte, error) { + type jsonKey struct { + Address string `json:"address"` + PropID uint64 `json:"prop_id"` + } + return json.Marshal(jsonKey{ + Address: key.K1().String(), + PropID: key.K2(), + }) +} + +func (t TemporaryKeyCodec) DecodeJSON(b []byte) (collections.Pair[sdk.AccAddress, uint64], error) { + var data struct { + Address string `json:"address"` + PropID uint64 `json:"prop_id"` + } + if err := json.Unmarshal(b, &data); err != nil { + return collections.Pair[sdk.AccAddress, uint64]{}, err + } + + addr, err := sdk.AccAddressFromBech32(data.Address) + if err != nil { + return collections.Pair[sdk.AccAddress, uint64]{}, err + } + + return collections.Join(addr, data.PropID), nil +} + +func (t TemporaryKeyCodec) Stringify(key collections.Pair[sdk.AccAddress, uint64]) string { + return fmt.Sprintf("%s/%d", key.K1().String(), key.K2()) +} + +func (t TemporaryKeyCodec) KeyType() string { + return "sanction.TemporaryKey" +} + +func (t TemporaryKeyCodec) EncodeNonTerminal(buffer []byte, key collections.Pair[sdk.AccAddress, uint64]) (int, error) { + return t.Encode(buffer, key) +} + +func (t TemporaryKeyCodec) DecodeNonTerminal(buffer []byte) (int, collections.Pair[sdk.AccAddress, uint64], error) { + return t.Decode(buffer) +} + +func (t TemporaryKeyCodec) SizeNonTerminal(key collections.Pair[sdk.AccAddress, uint64]) int { + return t.Size(key) +} + +func (t TemporaryKeyCodec) EncodePrefix(addr sdk.AccAddress) []byte { + prefix := make([]byte, 1+len(addr)) + prefix[0] = byte(len(addr)) + copy(prefix[1:], addr) + return prefix +} + +// ProposalIndexKeyCodec implements KeyCodec[collections.Pair[uint64, sdk.AccAddress]] +// Format: +type ProposalIndexKeyCodec struct{} + +func (p ProposalIndexKeyCodec) Encode(buffer []byte, key collections.Pair[uint64, sdk.AccAddress]) (int, error) { + propID := key.K1() + addr := key.K2() + + size := p.Size(key) + if len(buffer) < size { + return 0, fmt.Errorf("buffer too small: need %d, got %d", size, len(buffer)) + } + + binary.BigEndian.PutUint64(buffer[0:8], propID) + buffer[8] = byte(len(addr)) + copy(buffer[9:], addr) + + return size, nil +} + +func (p ProposalIndexKeyCodec) Decode(buffer []byte) (int, collections.Pair[uint64, sdk.AccAddress], error) { + if len(buffer) < 8+1 { + return 0, collections.Pair[uint64, sdk.AccAddress]{}, fmt.Errorf("buffer too short: got %d bytes", len(buffer)) + } + + propID := binary.BigEndian.Uint64(buffer[0:8]) + addrLen := int(buffer[8]) + size := 8 + 1 + addrLen + + if len(buffer) < size { + return 0, collections.Pair[uint64, sdk.AccAddress]{}, fmt.Errorf("invalid address length: need %d, got %d", size, len(buffer)) + } + + addr := sdk.AccAddress(buffer[9 : 9+addrLen]) + + return size, collections.Join(propID, addr), nil +} + +func (p ProposalIndexKeyCodec) Size(key collections.Pair[uint64, sdk.AccAddress]) int { + return 8 + 1 + len(key.K2()) +} + +func (p ProposalIndexKeyCodec) EncodeJSON(key collections.Pair[uint64, sdk.AccAddress]) ([]byte, error) { + type jsonKey struct { + PropID uint64 `json:"prop_id"` + Address string `json:"address"` + } + return json.Marshal(jsonKey{ + PropID: key.K1(), + Address: key.K2().String(), + }) +} + +func (p ProposalIndexKeyCodec) DecodeJSON(b []byte) (collections.Pair[uint64, sdk.AccAddress], error) { + var data struct { + PropID uint64 `json:"prop_id"` + Address string `json:"address"` + } + if err := json.Unmarshal(b, &data); err != nil { + return collections.Pair[uint64, sdk.AccAddress]{}, err + } + + addr, err := sdk.AccAddressFromBech32(data.Address) + if err != nil { + return collections.Pair[uint64, sdk.AccAddress]{}, err + } + + return collections.Join(data.PropID, addr), nil +} + +func (p ProposalIndexKeyCodec) Stringify(key collections.Pair[uint64, sdk.AccAddress]) string { + return fmt.Sprintf("%d/%s", key.K1(), key.K2().String()) +} + +func (p ProposalIndexKeyCodec) KeyType() string { + return "sanction.ProposalIndexKey" +} + +func (p ProposalIndexKeyCodec) EncodeNonTerminal(buffer []byte, key collections.Pair[uint64, sdk.AccAddress]) (int, error) { + return p.Encode(buffer, key) +} + +func (p ProposalIndexKeyCodec) DecodeNonTerminal(buffer []byte) (int, collections.Pair[uint64, sdk.AccAddress], error) { + return p.Decode(buffer) +} + +func (p ProposalIndexKeyCodec) SizeNonTerminal(key collections.Pair[uint64, sdk.AccAddress]) int { + return p.Size(key) +} diff --git a/x/sanction/keeper/export_test.go b/x/sanction/keeper/export_test.go index b4d8ef2283..4b4de64c66 100644 --- a/x/sanction/keeper/export_test.go +++ b/x/sanction/keeper/export_test.go @@ -3,8 +3,7 @@ package keeper import ( "context" - storetypes "cosmossdk.io/store/types" - + "cosmossdk.io/core/store" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -45,9 +44,9 @@ func (k Keeper) WithUnsanctionableAddrs(unsanctionableAddrs map[string]bool) Kee return k } -// StoreKey, for unit tests, exposes this keeper's storekey. -func (k Keeper) StoreKey() storetypes.StoreKey { - return k.storeKey +// KVStore, for unit tests, returns a KVStore for the given context from the keeper's StoreService. +func (k Keeper) KVStore(ctx sdk.Context) store.KVStore { + return k.StoreService.OpenKVStore(ctx) } // MsgSanctionTypeURL, for unit tests, exposes this keeper's msgSanctionTypeURL. @@ -71,23 +70,23 @@ func (k Keeper) GetParamAsCoinsOrDefault(ctx sdk.Context, name string, dflt sdk. } // GetLatestTempEntry, for unit tests, exposes this keeper's getLatestTempEntry function. -func (k Keeper) GetLatestTempEntry(store storetypes.KVStore, addr sdk.AccAddress) []byte { - return k.getLatestTempEntry(store, addr) +func (k Keeper) GetLatestTempEntry(ctx sdk.Context, addr sdk.AccAddress) []byte { + return k.getLatestTempEntry(ctx, addr) } // GetParam, for unit tests, exposes this keeper's getParam function. -func (k Keeper) GetParam(store storetypes.KVStore, name string) (string, bool) { - return k.getParam(store, name) +func (k Keeper) GetParam(ctx sdk.Context, name string) (string, bool) { + return k.getParam(ctx, name) } // SetParam, for unit tests, exposes this keeper's setParam function. -func (k Keeper) SetParam(store storetypes.KVStore, name, value string) { - k.setParam(store, name, value) +func (k Keeper) SetParam(ctx sdk.Context, name, value string) { + k.setParam(ctx, name, value) } // DeleteParam, for unit tests, exposes this keeper's deleteParam function. -func (k Keeper) DeleteParam(store storetypes.KVStore, name string) { - k.deleteParam(store, name) +func (k Keeper) DeleteParam(ctx sdk.Context, name string) { + k.deleteParam(ctx, name) } // ProposalGovHook, for unit tests, exposes this keeper's proposalGovHook function. diff --git a/x/sanction/keeper/genesis.go b/x/sanction/keeper/genesis.go index 45963ce135..0073ad6417 100644 --- a/x/sanction/keeper/genesis.go +++ b/x/sanction/keeper/genesis.go @@ -65,10 +65,12 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *sanction.GenesisState { // This is designed for use with ExportGenesis. See also IterateSanctionedAddresses. func (k Keeper) GetAllSanctionedAddresses(ctx sdk.Context) []string { var rv []string - k.IterateSanctionedAddresses(ctx, func(addr sdk.AccAddress) bool { + if err := k.IterateSanctionedAddresses(ctx, func(addr sdk.AccAddress) bool { rv = append(rv, addr.String()) return false - }) + }); err != nil { + panic(fmt.Errorf("failed to iterate sanctioned addresses: %w", err)) + } return rv } @@ -76,7 +78,7 @@ func (k Keeper) GetAllSanctionedAddresses(ctx sdk.Context) []string { // This is designed for use with ExportGenesis. See also IterateTemporaryEntries. func (k Keeper) GetAllTemporaryEntries(ctx sdk.Context) []*sanction.TemporaryEntry { var rv []*sanction.TemporaryEntry - k.IterateTemporaryEntries(ctx, nil, func(addr sdk.AccAddress, id uint64, isSanction bool) bool { + if err := k.IterateTemporaryEntries(ctx, nil, func(addr sdk.AccAddress, id uint64, isSanction bool) bool { status := sanction.TEMP_STATUS_SANCTIONED if !isSanction { status = sanction.TEMP_STATUS_UNSANCTIONED @@ -87,6 +89,8 @@ func (k Keeper) GetAllTemporaryEntries(ctx sdk.Context) []*sanction.TemporaryEnt Status: status, }) return false - }) + }); err != nil { + panic(fmt.Errorf("failed to iterate temporary entries: %w", err)) + } return rv } diff --git a/x/sanction/keeper/genesis_test.go b/x/sanction/keeper/genesis_test.go index c4c166e7e8..e26b2c6e7a 100644 --- a/x/sanction/keeper/genesis_test.go +++ b/x/sanction/keeper/genesis_test.go @@ -532,7 +532,7 @@ func (s *GenesisTestSuite) TestKeeper_ExportGenesis() { store := s.GetStore() s.Require().NotPanics(func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit) }, "deleting the ParamNameImmediateUnsanctionMinDeposit param entry") // Note: Not using UnsanctionAddresses here since I want to keep the temp entries for these addrs. s.Require().NotPanics(func() { diff --git a/x/sanction/keeper/gov_hooks.go b/x/sanction/keeper/gov_hooks.go index e41d644300..e1b4a19601 100644 --- a/x/sanction/keeper/gov_hooks.go +++ b/x/sanction/keeper/gov_hooks.go @@ -97,7 +97,9 @@ func (k Keeper) proposalGovHook(goCtx context.Context, proposalID uint64) error // C) The extra processing from calling DeleteGovPropTempEntries is probably on par with what's needed // to not always call it. // D) There's no risk of this deleting anything that shouldn't be deleted. - k.DeleteGovPropTempEntries(ctx, proposalID) + if err := k.DeleteGovPropTempEntries(ctx, proposalID); err != nil { + panic(fmt.Errorf("failed to delete temporary entries for proposal %d: %w", proposalID, err)) + } case govv1.StatusPassed: // Nothing to do. The processing of the proposal message does everything that's needed. default: diff --git a/x/sanction/keeper/grpc_query.go b/x/sanction/keeper/grpc_query.go index 6c0ce756f6..1bbfe211a4 100644 --- a/x/sanction/keeper/grpc_query.go +++ b/x/sanction/keeper/grpc_query.go @@ -6,6 +6,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "cosmossdk.io/collections" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" @@ -38,22 +40,28 @@ func (k Keeper) SanctionedAddresses(goCtx context.Context, req *sanction.QuerySa if req != nil { pagination = req.Pagination } - - resp := &sanction.QuerySanctionedAddressesResponse{} ctx := sdk.UnwrapSDKContext(goCtx) - store := k.getSanctionedAddressPrefixStore(ctx) - resp.Pagination, err = query.Paginate( - store, pagination, - func(key, _ []byte) error { - addrBz, _ := ParseLengthPrefixedBz(key) - resp.Addresses = append(resp.Addresses, sdk.AccAddress(addrBz).String()) - return nil + resp := &sanction.QuerySanctionedAddressesResponse{ + Addresses: []string{}, + } + + // Use Collections pagination + results, pageRes, err := query.CollectionPaginate( + ctx, + k.SanctionedAddressesStore, + pagination, + func(key sdk.AccAddress, _ []byte) (string, error) { + return key.String(), nil }, ) + if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + resp.Addresses = results + resp.Pagination = pageRes + return resp, nil } @@ -70,26 +78,149 @@ func (k Keeper) TemporaryEntries(goCtx context.Context, req *sanction.QueryTempo } } } - - resp := &sanction.QueryTemporaryEntriesResponse{} ctx := sdk.UnwrapSDKContext(goCtx) - store, pre := k.getTemporaryEntryPrefixStore(ctx, addr) - resp.Pagination, err = query.Paginate( - store, pagination, - func(key, value []byte) error { - kAddr, propID := ParseTemporaryKey(ConcatBz(pre, key)) - entry := sanction.TemporaryEntry{ - Address: kAddr.String(), - ProposalId: propID, + + if len(addr) > 0 { + return k.temporaryEntriesForAddress(ctx, addr, pagination) + } + + // Unfiltered query - use standard pagination + results, pageRes, err := query.CollectionPaginate( + ctx, + k.TemporaryEntriesStore, + pagination, + func(key collections.Pair[sdk.AccAddress, uint64], value []byte) (*sanction.TemporaryEntry, error) { + return &sanction.TemporaryEntry{ + Address: key.K1().String(), + ProposalId: key.K2(), Status: ToTempStatus(value), - } - resp.Entries = append(resp.Entries, &entry) - return nil + }, nil }, ) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &sanction.QueryTemporaryEntriesResponse{ + Entries: results, + Pagination: pageRes, + }, nil +} + +// temporaryEntriesForAddress handles filtered queries for a specific address +func (k Keeper) temporaryEntriesForAddress( + ctx sdk.Context, + addr sdk.AccAddress, + pagination *query.PageRequest, +) (*sanction.QueryTemporaryEntriesResponse, error) { + limit := uint64(100) + offset := uint64(0) + reverse := false + countTotal := false + var startPropID *uint64 + + if pagination != nil { + if pagination.Limit > 0 { + limit = pagination.Limit + } + reverse = pagination.Reverse + countTotal = pagination.CountTotal + if len(pagination.Key) == 8 { + pid := sdk.BigEndianToUint64(pagination.Key) + startPropID = &pid + offset = 0 + } else { + offset = pagination.Offset + } + } + + // Construct prefix matching TemporaryKeyCodec: + prefixBytes := make([]byte, 1+len(addr)) + prefixBytes[0] = byte(len(addr)) + copy(prefixBytes[1:], addr) + + // Calculate prefix end + prefixEnd := prefixEndBytes(prefixBytes) + + // Collect ALL matching entries first + iter, err := k.TemporaryEntriesStore.IterateRaw(ctx, prefixBytes, prefixEnd, collections.OrderAscending) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + defer iter.Close() //nolint:errcheck // close error safe to ignore in this context. + + items := []*sanction.TemporaryEntry{} // never nil + var total uint64 + + for ; iter.Valid(); iter.Next() { + kv, err := iter.KeyValue() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + pid := kv.Key.K2() + + // :white_check_mark: FIX: Handle both forward and reverse key pagination + if startPropID != nil { + if !reverse && pid < *startPropID { + // Forward mode: skip entries before startPropID + if countTotal { + total++ + } + continue + } + if reverse && pid > *startPropID { + // Reverse mode: skip entries after startPropID + if countTotal { + total++ + } + continue + } + } + + if countTotal { + total++ + } + + items = append(items, &sanction.TemporaryEntry{ + Address: kv.Key.K1().String(), + ProposalId: pid, + Status: ToTempStatus(kv.Value), + }) + } + + // Apply reverse AFTER collection + if reverse { + for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 { + items[i], items[j] = items[j], items[i] + } + } + + // Apply offset/limit AFTER collection + //nolint:gosec // safe conversion: offset/limit are always small (<2^63) from pagination + start := int(offset) + if start > len(items) { + start = len(items) + } + //nolint:gosec // safe conversion: limit is small (<2^63) + end := start + int(limit) + if end > len(items) { + end = len(items) + } + pageItems := items[start:end] + + resp := &sanction.QueryTemporaryEntriesResponse{ + Entries: pageItems, + Pagination: &query.PageResponse{ + Total: total, + }, + } + + if end < len(items) { + nextPropID := items[end].ProposalId + resp.Pagination.NextKey = sdk.Uint64ToBigEndian(nextPropID) + } return resp, nil } @@ -100,3 +231,22 @@ func (k Keeper) Params(goCtx context.Context, _ *sanction.QueryParamsRequest) (* resp.Params = k.GetParams(ctx) return resp, nil } + +// prefixEndBytes returns the []byte that would end a range query for all []byte with a certain prefix +func prefixEndBytes(prefix []byte) []byte { + if len(prefix) == 0 { + return nil + } + + end := make([]byte, len(prefix)) + copy(end, prefix) + + for i := len(end) - 1; i >= 0; i-- { + end[i]++ + if end[i] != 0 { + return end + } + } + + return nil +} diff --git a/x/sanction/keeper/keeper.go b/x/sanction/keeper/keeper.go index ea05b5b43a..675ed54be0 100644 --- a/x/sanction/keeper/keeper.go +++ b/x/sanction/keeper/keeper.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "cosmossdk.io/store/prefix" - storetypes "cosmossdk.io/store/types" + "cosmossdk.io/collections" + "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,8 +17,7 @@ import ( ) type Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey + cdc codec.BinaryCodec govKeeper sanction.GovKeeper @@ -29,32 +28,98 @@ type Keeper struct { msgSanctionTypeURL string msgUnsanctionTypeURL string msgExecLegacyContentTypeURL string + + StoreService store.KVStoreService + + // Collections Schema + Schema collections.Schema + + // Params collection: name -> value (as []byte) + ParameterStore collections.Map[string, []byte] + + // Sanctioned addresses: address -> []byte{0x01} + SanctionedAddressesStore collections.Map[sdk.AccAddress, []byte] + + // Temporary entries with backward-compatible key encoding + // Key: (address, govPropID) -> Value: []byte{0x01 or 0x00} + TemporaryEntriesStore collections.Map[collections.Pair[sdk.AccAddress, uint64], []byte] + + // Proposal index with backward-compatible key encoding + // Key: (govPropID, address) -> Value: []byte{0x01 or 0x00} + ProposalIndex collections.Map[collections.Pair[uint64, sdk.AccAddress], []byte] } func NewKeeper( cdc codec.BinaryCodec, - storeKey storetypes.StoreKey, + storeService store.KVStoreService, bankKeeper sanction.BankKeeper, govKeeper *govkeeper.Keeper, authority string, unsanctionableAddrs []sdk.AccAddress, ) Keeper { - rv := Keeper{ + sb := collections.NewSchemaBuilder(storeService) + + k := Keeper{ cdc: cdc, - storeKey: storeKey, - govKeeper: WrapGovKeeper(govKeeper), authority: authority, + govKeeper: WrapGovKeeper(govKeeper), unsanctionableAddrs: make(map[string]bool), msgSanctionTypeURL: sdk.MsgTypeURL(&sanction.MsgSanction{}), msgUnsanctionTypeURL: sdk.MsgTypeURL(&sanction.MsgUnsanction{}), msgExecLegacyContentTypeURL: sdk.MsgTypeURL(&govv1.MsgExecLegacyContent{}), + + // Params: simple string -> []byte map + ParameterStore: collections.NewMap( + sb, + ParamsPrefix, + "params", + collections.StringKey, + collections.BytesValue, + ), + + // Sanctioned addresses: use custom AccAddress codec + SanctionedAddressesStore: collections.NewMap( + sb, + SanctionedPrefix, + "sanctioned_addresses", + sanction.AccAddressKey{}, + collections.BytesValue, + ), + + // Temporary entries: (AccAddress, uint64) -> []byte with custom codec + // Collections adds 0x02 prefix, codec handles [len][addr][propID] + TemporaryEntriesStore: collections.NewMap( + sb, + TemporaryPrefix, + "temporary_entries", + sanction.TemporaryKeyCodec{}, + collections.BytesValue, + ), + + // Proposal index: (uint64, AccAddress) -> []byte with custom codec + // Collections adds 0x03 prefix, codec handles [propID][len][addr] + ProposalIndex: collections.NewMap( + sb, + ProposalIndexPrefix, + "proposal_index", + sanction.ProposalIndexKeyCodec{}, + collections.BytesValue, + ), + StoreService: storeService, } + for _, addr := range unsanctionableAddrs { - // using string(addr) here instead of addr.String() to cut down on the need to bech32 encode things. - rv.unsanctionableAddrs[string(addr)] = true + k.unsanctionableAddrs[string(addr)] = true } - bankKeeper.AppendSendRestriction(rv.SendRestrictionFn) - return rv + + schema, err := sb.Build() + if err != nil { + panic(fmt.Errorf("building sanction keeper schema: %w", err)) + } + k.Schema = schema + + bankKeeper.AppendSendRestriction(k.SendRestrictionFn) + return k } // GetAuthority returns this module's authority string. @@ -68,49 +133,63 @@ func (k Keeper) IsSanctionedAddr(goCtx context.Context, addr sdk.AccAddress) boo return false } ctx := sdk.UnwrapSDKContext(goCtx) - store := ctx.KVStore(k.storeKey) - tempEntry := k.getLatestTempEntry(store, addr) + tempEntry := k.getLatestTempEntry(ctx, addr) if IsSanctionBz(tempEntry) { return true } if IsUnsanctionBz(tempEntry) { return false } - key := CreateSanctionedAddrKey(addr) - return store.Has(key) + // Check permanent sanction + has, err := k.SanctionedAddressesStore.Has(ctx, addr) + if err != nil { + return false + } + return has } // SanctionAddresses creates permanent sanctioned address entries for each of the provided addresses. // Also deletes any temporary entries for each address. func (k Keeper) SanctionAddresses(ctx sdk.Context, addrs ...sdk.AccAddress) error { - store := ctx.KVStore(k.storeKey) val := []byte{SanctionB} for _, addr := range addrs { if k.IsAddrThatCannotBeSanctioned(addr) { return errors.ErrUnsanctionableAddr.Wrap(addr.String()) } - key := CreateSanctionedAddrKey(addr) - store.Set(key, val) + + if err := k.SanctionedAddressesStore.Set(ctx, addr, val); err != nil { + return fmt.Errorf("setting sanctioned address %s: %w", addr.String(), err) + } + if err := ctx.EventManager().EmitTypedEvent(sanction.NewEventAddressSanctioned(addr)); err != nil { - return err + return fmt.Errorf("emitting sanction event for %s: %w", addr.String(), err) } } - k.DeleteAddrTempEntries(ctx, addrs...) + + if err := k.DeleteAddrTempEntries(ctx, addrs...); err != nil { + return fmt.Errorf("deleting temp entries: %w", err) + } + return nil } // UnsanctionAddresses deletes any sanctioned address entries for each provided address. // Also deletes any temporary entries for each address. func (k Keeper) UnsanctionAddresses(ctx sdk.Context, addrs ...sdk.AccAddress) error { - store := ctx.KVStore(k.storeKey) for _, addr := range addrs { - key := CreateSanctionedAddrKey(addr) - store.Delete(key) + if err := k.SanctionedAddressesStore.Remove(ctx, addr); err != nil { + return fmt.Errorf("removing sanctioned address %s: %w", addr.String(), err) + } + if err := ctx.EventManager().EmitTypedEvent(sanction.NewEventAddressUnsanctioned(addr)); err != nil { - return err + return fmt.Errorf("emitting unsanction event for %s: %w", addr.String(), err) } } - k.DeleteAddrTempEntries(ctx, addrs...) + + if err := k.DeleteAddrTempEntries(ctx, addrs...); err != nil { + return fmt.Errorf("deleting temp entries: %w", err) + } + return nil } @@ -126,16 +205,21 @@ func (k Keeper) AddTemporaryUnsanction(ctx sdk.Context, govPropID uint64, addrs // addTempEntries adds a temporary entry with the given value and gov prop id for each address given. func (k Keeper) addTempEntries(ctx sdk.Context, value byte, govPropID uint64, addrs []sdk.AccAddress) error { - store := ctx.KVStore(k.storeKey) val := []byte{value} for _, addr := range addrs { if value == SanctionB && k.IsAddrThatCannotBeSanctioned(addr) { return errors.ErrUnsanctionableAddr.Wrap(addr.String()) } - key := CreateTemporaryKey(addr, govPropID) - store.Set(key, val) - indKey := CreateProposalTempIndexKey(govPropID, addr) - store.Set(indKey, val) + + key := collections.Join(addr, govPropID) + if err := k.TemporaryEntriesStore.Set(ctx, key, val); err != nil { + return fmt.Errorf("setting temporary entry for %s/%d: %w", addr.String(), govPropID, err) + } + + indexKey := collections.Join(govPropID, addr) + if err := k.ProposalIndex.Set(ctx, indexKey, val); err != nil { + return fmt.Errorf("setting proposal index for %d/%s: %w", govPropID, addr.String(), err) + } if err := ctx.EventManager().EmitTypedEvent(NewTempEvent(value, addr)); err != nil { return err } @@ -144,91 +228,124 @@ func (k Keeper) addTempEntries(ctx sdk.Context, value byte, govPropID uint64, ad } // getLatestTempEntry gets the most recent temporary entry for the given address. -func (k Keeper) getLatestTempEntry(store storetypes.KVStore, addr sdk.AccAddress) []byte { +func (k Keeper) getLatestTempEntry(ctx sdk.Context, addr sdk.AccAddress) []byte { if len(addr) == 0 { return nil } - pre := CreateTemporaryAddrPrefix(addr) - preStore := prefix.NewStore(store, pre) - iter := preStore.ReverseIterator(nil, nil) - defer iter.Close() //nolint:errcheck // ignoring close error on iterator: not critical for this context. - if iter.Valid() { - return iter.Value() + + var latestValue []byte + var latestPropID uint64 + found := false + + // Walk all temporary entries and find the one for this address with highest propID + err := k.TemporaryEntriesStore.Walk(ctx, nil, + func(key collections.Pair[sdk.AccAddress, uint64], value []byte) (stop bool, err error) { + // Only consider entries for this address + if !key.K1().Equals(addr) { + return false, nil + } + + propID := key.K2() + if !found || propID > latestPropID { + latestPropID = propID + latestValue = value + found = true + } + return false, nil + }) + + if err != nil { + return nil } - return nil + + return latestValue } // DeleteGovPropTempEntries deletes the temporary entries for the given proposal id. -func (k Keeper) DeleteGovPropTempEntries(ctx sdk.Context, govPropID uint64) { - var toRemove [][]byte - k.IterateProposalIndexEntries(ctx, &govPropID, func(cbGovPropID uint64, cbAddr sdk.AccAddress) bool { - toRemove = append(toRemove, - CreateTemporaryKey(cbAddr, cbGovPropID), - CreateProposalTempIndexKey(cbGovPropID, cbAddr), - ) - return false - }) - if len(toRemove) > 0 { - store := ctx.KVStore(k.storeKey) - for _, key := range toRemove { - store.Delete(key) +func (k Keeper) DeleteGovPropTempEntries(ctx sdk.Context, govPropID uint64) error { + var toRemove []collections.Pair[sdk.AccAddress, uint64] + var indexToRemove []collections.Pair[uint64, sdk.AccAddress] + + err := k.ProposalIndex.Walk(ctx, nil, + func(key collections.Pair[uint64, sdk.AccAddress], _ []byte) (stop bool, err error) { + if key.K1() == govPropID { + addr := key.K2() + toRemove = append(toRemove, collections.Join(addr, govPropID)) + indexToRemove = append(indexToRemove, key) + } + return false, nil + }) + + if err != nil { + return fmt.Errorf("walking proposal index for %d: %w", govPropID, err) + } + + for _, key := range toRemove { + if err := k.TemporaryEntriesStore.Remove(ctx, key); err != nil { + return fmt.Errorf("removing temporary entry %v: %w", key, err) + } + } + + for _, key := range indexToRemove { + if err := k.ProposalIndex.Remove(ctx, key); err != nil { + return fmt.Errorf("removing proposal index entry %v: %w", key, err) } } + + return nil } // DeleteAddrTempEntries deletes all temporary entries for each given address. -func (k Keeper) DeleteAddrTempEntries(ctx sdk.Context, addrs ...sdk.AccAddress) { +func (k Keeper) DeleteAddrTempEntries(ctx sdk.Context, addrs ...sdk.AccAddress) error { if len(addrs) == 0 { - return - } - var toRemove [][]byte - callback := func(cbAddr sdk.AccAddress, cbGovPropId uint64, _ bool) bool { - toRemove = append(toRemove, - CreateTemporaryKey(cbAddr, cbGovPropId), - CreateProposalTempIndexKey(cbGovPropId, cbAddr), - ) - return false + return nil } for _, addr := range addrs { - if len(addr) > 0 { - k.IterateTemporaryEntries(ctx, addr, callback) - } - } - if len(toRemove) > 0 { - store := ctx.KVStore(k.storeKey) - for _, key := range toRemove { - store.Delete(key) + if len(addr) == 0 { + continue } - } -} -// getSanctionedAddressPrefixStore returns a kv store prefixed for sanctioned addresses, and the prefix bytes. -func (k Keeper) getSanctionedAddressPrefixStore(ctx sdk.Context) storetypes.KVStore { - return prefix.NewStore(ctx.KVStore(k.storeKey), SanctionedPrefix) -} + var tempKeys []collections.Pair[sdk.AccAddress, uint64] + var indexKeys []collections.Pair[uint64, sdk.AccAddress] -// IterateSanctionedAddresses iterates over all of the permanently sanctioned addresses. -// The callback takes in the sanctioned address and should return whether to stop iteration (true = stop, false = keep going). -func (k Keeper) IterateSanctionedAddresses(ctx sdk.Context, cb func(addr sdk.AccAddress) (stop bool)) { - store := k.getSanctionedAddressPrefixStore(ctx) + // Collect all temporary entries for this address + err := k.TemporaryEntriesStore.Walk(ctx, nil, + func(key collections.Pair[sdk.AccAddress, uint64], _ []byte) (stop bool, err error) { + if key.K1().Equals(addr) { + tempKeys = append(tempKeys, key) + indexKeys = append(indexKeys, collections.Join(key.K2(), addr)) + } + return false, nil + }) - iter := store.Iterator(nil, nil) - defer iter.Close() //nolint:errcheck // ignoring close error on iterator: not critical for this context. + if err != nil { + return fmt.Errorf("walking temp entries for %s: %w", addr.String(), err) + } + + // Delete temporary entries + for _, key := range tempKeys { + if err := k.TemporaryEntriesStore.Remove(ctx, key); err != nil { + return fmt.Errorf("removing temp entry %v: %w", key, err) + } + } - for ; iter.Valid(); iter.Next() { - addr, _ := ParseLengthPrefixedBz(iter.Key()) - if cb(addr) { - break + // Delete index entries + for _, key := range indexKeys { + if err := k.ProposalIndex.Remove(ctx, key); err != nil { + return fmt.Errorf("removing index entry %v: %w", key, err) + } } } + + return nil } -// getTemporaryEntryPrefixStore returns a kv store prefixed for temporary sanction/unsanction entries, and the prefix bytes used. -// If an addr is provided, the store is prefixed for just the given address. -// If addr is empty, it will be prefixed for all temporary entries. -func (k Keeper) getTemporaryEntryPrefixStore(ctx sdk.Context, addr sdk.AccAddress) (storetypes.KVStore, []byte) { - pre := CreateTemporaryAddrPrefix(addr) - return prefix.NewStore(ctx.KVStore(k.storeKey), pre), pre +// IterateSanctionedAddresses iterates over all of the permanently sanctioned addresses. +// The callback takes in the sanctioned address and should return whether to stop iteration (true = stop, false = keep going). +func (k Keeper) IterateSanctionedAddresses(ctx sdk.Context, cb func(addr sdk.AccAddress) (stop bool)) error { + return k.SanctionedAddressesStore.Walk(ctx, nil, func(addr sdk.AccAddress, _ []byte) (stop bool, err error) { + return cb(addr), nil + }) } // IterateTemporaryEntries iterates over each of the temporary entries. @@ -236,47 +353,28 @@ func (k Keeper) getTemporaryEntryPrefixStore(ctx sdk.Context, addr sdk.AccAddres // otherwise all entries are iterated. // The callback takes in the address in question, the governance proposal associated with it, and whether it's a sanction (true) or unsanction (false). // The callback should return whether to stop iteration (true = stop, false = keep going). -func (k Keeper) IterateTemporaryEntries(ctx sdk.Context, addr sdk.AccAddress, cb func(addr sdk.AccAddress, govPropID uint64, isSanction bool) (stop bool)) { - store, pre := k.getTemporaryEntryPrefixStore(ctx, addr) - - iter := store.Iterator(nil, nil) - defer iter.Close() //nolint:errcheck // ignoring close error on iterator: not critical for this context. - - for ; iter.Valid(); iter.Next() { - key := ConcatBz(pre, iter.Key()) - kAddr, govPropID := ParseTemporaryKey(key) - isSanction := IsSanctionBz(iter.Value()) - if cb(kAddr, govPropID, isSanction) { - break +func (k Keeper) IterateTemporaryEntries(ctx sdk.Context, addr sdk.AccAddress, cb func(addr sdk.AccAddress, govPropID uint64, isSanction bool) (stop bool)) error { + return k.TemporaryEntriesStore.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, uint64], value []byte) (stop bool, err error) { + if len(addr) > 0 && !key.K1().Equals(addr) { + return false, nil } - } -} -// getProposalIndexPrefixStore returns a kv store prefixed for the gov prop -> temporary sanction/unsanction index entries, -// and the prefix bytes used. -// If a gov prop id is provided, the store is prefixed for just that proposal. -// If not provided, it will be prefixed for all temp index entries. -func (k Keeper) getProposalIndexPrefixStore(ctx sdk.Context, govPropID *uint64) (storetypes.KVStore, []byte) { - pre := CreateProposalTempIndexPrefix(govPropID) - return prefix.NewStore(ctx.KVStore(k.storeKey), pre), pre + isSanction := IsSanctionBz(value) + return cb(key.K1(), key.K2(), isSanction), nil + }) } // IterateProposalIndexEntries iterates over all of the index entries for temp entries. // The callback takes in the gov prop id and address. // The callback should return whether to stop iteration (true = stop, false = keep going). -func (k Keeper) IterateProposalIndexEntries(ctx sdk.Context, govPropID *uint64, cb func(govPropID uint64, addr sdk.AccAddress) (stop bool)) { - store, pre := k.getProposalIndexPrefixStore(ctx, govPropID) - - iter := store.Iterator(nil, nil) - defer iter.Close() //nolint:errcheck // ignoring close error on iterator: not critical for this context. - - for ; iter.Valid(); iter.Next() { - key := ConcatBz(pre, iter.Key()) - kPropID, kAddr := ParseProposalTempIndexKey(key) - if cb(kPropID, kAddr) { - break +func (k Keeper) IterateProposalIndexEntries(ctx sdk.Context, govPropID *uint64, cb func(govPropID uint64, addr sdk.AccAddress) (stop bool)) error { + return k.ProposalIndex.Walk(ctx, nil, func(key collections.Pair[uint64, sdk.AccAddress], _ []byte) (stop bool, err error) { + if govPropID != nil && key.K1() != *govPropID { + return false, nil } - } + + return cb(key.K1(), key.K2()), nil + }) } // IsAddrThatCannotBeSanctioned returns true if the provided address is one of the ones that cannot be sanctioned. @@ -294,17 +392,23 @@ func (k Keeper) IsAddrThatCannotBeSanctioned(addr sdk.AccAddress) bool { func (k Keeper) GetParams(ctx sdk.Context) *sanction.Params { rv := sanction.DefaultParams() - k.IterateParams(ctx, func(name, value string) bool { - switch name { - case ParamNameImmediateSanctionMinDeposit: - rv.ImmediateSanctionMinDeposit = toCoinsOrDefault(value, rv.ImmediateSanctionMinDeposit) - case ParamNameImmediateUnsanctionMinDeposit: - rv.ImmediateUnsanctionMinDeposit = toCoinsOrDefault(value, rv.ImmediateUnsanctionMinDeposit) - default: - panic(fmt.Errorf("unknown param key: %q", name)) + // Get immediate sanction min deposit + sanctionDeposit, err := k.ParameterStore.Get(ctx, ParamNameImmediateSanctionMinDeposit) + if err == nil { + coins, parseErr := sdk.ParseCoinsNormalized(string(sanctionDeposit)) + if parseErr == nil { + rv.ImmediateSanctionMinDeposit = coins } - return false - }) + } + + // Get immediate unsanction min deposit + unsanctionDeposit, err := k.ParameterStore.Get(ctx, ParamNameImmediateUnsanctionMinDeposit) + if err == nil { + coins, parseErr := sdk.ParseCoinsNormalized(string(unsanctionDeposit)) + if parseErr == nil { + rv.ImmediateUnsanctionMinDeposit = coins + } + } return rv } @@ -312,77 +416,83 @@ func (k Keeper) GetParams(ctx sdk.Context) *sanction.Params { // SetParams sets the sanction module's params. // Providing a nil params will cause all params to be deleted (so that defaults are used). func (k Keeper) SetParams(ctx sdk.Context, params *sanction.Params) error { - store := ctx.KVStore(k.storeKey) if params == nil { - k.deleteParam(store, ParamNameImmediateSanctionMinDeposit) - k.deleteParam(store, ParamNameImmediateUnsanctionMinDeposit) - } else { - k.setParam(store, ParamNameImmediateSanctionMinDeposit, params.ImmediateSanctionMinDeposit.String()) - k.setParam(store, ParamNameImmediateUnsanctionMinDeposit, params.ImmediateUnsanctionMinDeposit.String()) + params = &sanction.Params{ + ImmediateSanctionMinDeposit: sanction.DefaultImmediateSanctionMinDeposit, + ImmediateUnsanctionMinDeposit: sanction.DefaultImmediateUnsanctionMinDeposit, + } + } + + if err := k.ParameterStore.Set(ctx, ParamNameImmediateSanctionMinDeposit, + []byte(params.ImmediateSanctionMinDeposit.String())); err != nil { + return fmt.Errorf("setting immediate sanction min deposit: %w", err) } + + if err := k.ParameterStore.Set(ctx, ParamNameImmediateUnsanctionMinDeposit, + []byte(params.ImmediateUnsanctionMinDeposit.String())); err != nil { + return fmt.Errorf("setting immediate unsanction min deposit: %w", err) + } + return ctx.EventManager().EmitTypedEvent(&sanction.EventParamsUpdated{}) } // IterateParams iterates over all params entries. // The callback takes in the name and value, and should return whether to stop iteration (true = stop, false = keep going). -func (k Keeper) IterateParams(ctx sdk.Context, cb func(name, value string) (stop bool)) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), ParamsPrefix) - - iter := store.Iterator(nil, nil) - defer iter.Close() //nolint:errcheck // ignoring close error on iterator: not critical for this context. - - for ; iter.Valid(); iter.Next() { - if cb(string(iter.Key()), string(iter.Value())) { - break - } - } +func (k Keeper) IterateParams(ctx sdk.Context, cb func(name, value string) (stop bool)) error { + return k.ParameterStore.Walk(ctx, nil, func(name string, value []byte) (stop bool, err error) { + return cb(name, string(value)), nil + }) } // GetImmediateSanctionMinDeposit gets the minimum deposit for a sanction to happen immediately. func (k Keeper) GetImmediateSanctionMinDeposit(ctx sdk.Context) sdk.Coins { - return k.getParamAsCoinsOrDefault( - ctx, - ParamNameImmediateSanctionMinDeposit, - sanction.DefaultImmediateSanctionMinDeposit, - ) + params := k.GetParams(ctx) + return params.ImmediateSanctionMinDeposit } // GetImmediateUnsanctionMinDeposit gets the minimum deposit for an unsanction to happen immediately. func (k Keeper) GetImmediateUnsanctionMinDeposit(ctx sdk.Context) sdk.Coins { - return k.getParamAsCoinsOrDefault( - ctx, - ParamNameImmediateUnsanctionMinDeposit, - sanction.DefaultImmediateUnsanctionMinDeposit, - ) + params := k.GetParams(ctx) + return params.ImmediateUnsanctionMinDeposit } // getParam returns a param value and whether it existed. -func (k Keeper) getParam(store storetypes.KVStore, name string) (string, bool) { - key := CreateParamKey(name) - if store.Has(key) { - return string(store.Get(key)), true +// +//nolint:unused // used only in test cases +func (k Keeper) getParam(ctx sdk.Context, name string) (string, bool) { + key, err := k.ParameterStore.Get(ctx, name) + if err != nil { + return "", false } - return "", false + + return string(key), true } // setParam sets a param value. -func (k Keeper) setParam(store storetypes.KVStore, name, value string) { - key := CreateParamKey(name) - val := []byte(value) - store.Set(key, val) +// +//nolint:unused // used only in test cases +func (k Keeper) setParam(ctx sdk.Context, name, value string) { + if err := k.ParameterStore.Set(ctx, name, []byte(value)); err != nil { + panic(fmt.Errorf("failed to set parameter %q: %w", name, err)) + } } // deleteParam deletes a param value. -func (k Keeper) deleteParam(store storetypes.KVStore, name string) { - key := CreateParamKey(name) - store.Delete(key) +// +//nolint:unused // used only in test cases +func (k Keeper) deleteParam(ctx sdk.Context, name string) { + if err := k.ParameterStore.Remove(ctx, name); err != nil { + panic(fmt.Errorf("failed to delete parameter %q: %w", name, err)) + } } // getParamAsCoinsOrDefault gets a param value and converts it to a coins if possible. // If the param doesn't exist, the default is returned. // If the param's value cannot be converted to a Coins, the default is returned. +// +//nolint:unused // used only in test cases func (k Keeper) getParamAsCoinsOrDefault(ctx sdk.Context, name string, dflt sdk.Coins) sdk.Coins { - coins, has := k.getParam(ctx.KVStore(k.storeKey), name) + coins, has := k.getParam(ctx, name) if !has { return dflt } @@ -390,6 +500,8 @@ func (k Keeper) getParamAsCoinsOrDefault(ctx sdk.Context, name string, dflt sdk. } // toCoinsOrDefault converts a string to coins if possible or else returns the provided default. +// +//nolint:unused // used only in test cases func toCoinsOrDefault(coins string, dflt sdk.Coins) sdk.Coins { rv, err := sdk.ParseCoinsNormalized(coins) if err != nil { diff --git a/x/sanction/keeper/keeper_test.go b/x/sanction/keeper/keeper_test.go index b8e4cc0855..dd6e032ceb 100644 --- a/x/sanction/keeper/keeper_test.go +++ b/x/sanction/keeper/keeper_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/suite" + "cosmossdk.io/collections" sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -887,7 +887,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { var expected []byte var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, nil) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, nil) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -897,7 +897,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { var expected []byte var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, sdk.AccAddress{}) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, sdk.AccAddress{}) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -907,7 +907,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { var expected []byte var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, s.addr1) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, s.addr1) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -920,7 +920,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { expected := []byte{keeper.SanctionB} var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, addr) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, addr) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -933,7 +933,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { expected := []byte{keeper.UnsanctionB} var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, addr) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, addr) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -949,7 +949,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { expected := []byte{keeper.SanctionB} var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, addr) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, addr) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -965,7 +965,7 @@ func (s *KeeperTestSuite) TestKeeper_getLatestTempEntry() { expected := []byte{keeper.UnsanctionB} var actual []byte testFunc := func() { - actual = s.Keeper.GetLatestTempEntry(store, addr) + actual = s.Keeper.GetLatestTempEntry(s.SdkCtx, addr) } s.Require().NotPanics(testFunc, "getLatestTempEntry") s.Assert().Equal(expected, actual, "getLatestTempEntry result") @@ -1680,12 +1680,11 @@ func (s *KeeperTestSuite) TestKeeper_GetSetParams() { sanction.DefaultImmediateSanctionMinDeposit = sdk.NewCoins(sdk.NewInt64Coin("sanct", 93)) sanction.DefaultImmediateUnsanctionMinDeposit = sdk.NewCoins(sdk.NewInt64Coin("usanct", 49)) - store := s.GetStore() s.Require().NotPanics(func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateSanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateSanctionMinDeposit) }, "deleteParam(%q)", keeper.ParamNameImmediateSanctionMinDeposit) s.Require().NotPanics(func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit) }, "deleteParam(%q)", keeper.ParamNameImmediateUnsanctionMinDeposit) s.Run("get with no entries in store", func() { @@ -1802,12 +1801,11 @@ func (s *KeeperTestSuite) TestKeeper_IterateParams() { value string } - store := s.GetStore() s.Require().NotPanics(func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateSanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateSanctionMinDeposit) }, "deleteParam(%q)", keeper.ParamNameImmediateSanctionMinDeposit) s.Require().NotPanics(func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit) }, "deleteParam(%q)", keeper.ParamNameImmediateUnsanctionMinDeposit) s.Run("no entries", func() { @@ -1834,7 +1832,7 @@ func (s *KeeperTestSuite) TestKeeper_IterateParams() { // Write them in reverse order from expected. for i := len(expected) - 1; i >= 0; i-- { s.Require().NotPanics(func() { - s.Keeper.SetParam(store, expected[i].key, expected[i].value) + s.Keeper.SetParam(s.SdkCtx, expected[i].key, expected[i].value) }, "setParam(%q, %q)", expected[i].key, expected[i].value) } @@ -1904,9 +1902,8 @@ func (s *KeeperTestSuite) TestKeeper_GetImmediateSanctionMinDeposit() { delete bool } - store := s.GetStore() testFuncSetSanct := func() { - s.Keeper.SetParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit, "98unsanct") + s.Keeper.SetParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit, "98unsanct") } s.Require().NotPanics(testFuncSetSanct, "setParam(%q, %q)", keeper.ParamNameImmediateUnsanctionMinDeposit, "98unsanct") @@ -1947,13 +1944,13 @@ func (s *KeeperTestSuite) TestKeeper_GetImmediateSanctionMinDeposit() { for _, p := range tc.prep { if p.set { testFuncSet := func() { - s.Keeper.SetParam(store, keeper.ParamNameImmediateSanctionMinDeposit, p.value) + s.Keeper.SetParam(s.SdkCtx, keeper.ParamNameImmediateSanctionMinDeposit, p.value) } s.Require().NotPanics(testFuncSet, "setParam(%q, %q)", keeper.ParamNameImmediateSanctionMinDeposit, p.value) } if p.delete { testFuncDelete := func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateSanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateSanctionMinDeposit) } s.Require().NotPanics(testFuncDelete, "deleteParam(%q)", keeper.ParamNameImmediateSanctionMinDeposit) } @@ -1992,9 +1989,8 @@ func (s *KeeperTestSuite) TestKeeper_GetImmediateUnsanctionMinDeposit() { delete bool } - store := s.GetStore() testFuncSetSanct := func() { - s.Keeper.SetParam(store, keeper.ParamNameImmediateSanctionMinDeposit, "99sanct") + s.Keeper.SetParam(s.SdkCtx, keeper.ParamNameImmediateSanctionMinDeposit, "99sanct") } s.Require().NotPanics(testFuncSetSanct, "setParam(%q, %q)", keeper.ParamNameImmediateSanctionMinDeposit, "99sanct") @@ -2035,13 +2031,13 @@ func (s *KeeperTestSuite) TestKeeper_GetImmediateUnsanctionMinDeposit() { for _, p := range tc.prep { if p.set { testFuncSet := func() { - s.Keeper.SetParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit, p.value) + s.Keeper.SetParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit, p.value) } s.Require().NotPanics(testFuncSet, "setParam(%q, %q)", keeper.ParamNameImmediateUnsanctionMinDeposit, p.value) } if p.delete { testFuncDelete := func() { - s.Keeper.DeleteParam(store, keeper.ParamNameImmediateUnsanctionMinDeposit) + s.Keeper.DeleteParam(s.SdkCtx, keeper.ParamNameImmediateUnsanctionMinDeposit) } s.Require().NotPanics(testFuncDelete, "deleteParam(%q)", keeper.ParamNameImmediateUnsanctionMinDeposit) } @@ -2057,7 +2053,6 @@ func (s *KeeperTestSuite) TestKeeper_GetImmediateUnsanctionMinDeposit() { } func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { - store := s.GetStore() var toDelete []string newParamName := "new param" @@ -2065,7 +2060,7 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { var actual string var ok bool testFuncGet := func() { - actual, ok = s.Keeper.GetParam(store, newParamName) + actual, ok = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet, "getParam(%q)", newParamName) s.Assert().Equal("", actual, "getParam(%q) result string", newParamName) @@ -2076,12 +2071,12 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { s.Run("set param new param", func() { var alreadyExists bool testFuncGet := func() { - _, alreadyExists = s.Keeper.GetParam(store, newParamName) + _, alreadyExists = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet, "getParam(%q) on param that should not exist yet", newParamName) s.Require().False(alreadyExists, "getParam(%q) result bool on param that should not exist yet", newParamName) testFuncSet := func() { - s.Keeper.SetParam(store, newParamName, newParamValue) + s.Keeper.SetParam(s.SdkCtx, newParamName, newParamValue) } s.Require().NotPanics(testFuncSet, "setParam(%q, %q)", newParamName, newParamValue) toDelete = append(toDelete, newParamName) @@ -2091,7 +2086,7 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { var actual string var ok bool testFuncGet := func() { - actual, ok = s.Keeper.GetParam(store, newParamName) + actual, ok = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet, "getParam(%q)", newParamName) s.Require().True(ok, "getParam(%q) result bool", newParamName) @@ -2102,14 +2097,14 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { name := "fruits" value := "bananas, apples, pears, papaya, pineapple, pomegranate" testFuncSet := func() { - s.Keeper.SetParam(store, name, value) + s.Keeper.SetParam(s.SdkCtx, name, value) } s.Require().NotPanics(testFuncSet, "setParam(%q, %q)", name, value) toDelete = append(toDelete, name) var actual string var ok bool testFuncGet := func() { - actual, ok = s.Keeper.GetParam(store, name) + actual, ok = s.Keeper.GetParam(s.SdkCtx, name) } s.Require().NotPanics(testFuncGet, "getParam(%q)", name) s.Assert().True(ok, "getParam(%q) result bool", name) @@ -2120,7 +2115,7 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { var actual string var ok bool testFuncGet := func() { - actual, ok = s.Keeper.GetParam(store, newParamName) + actual, ok = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet, "getParam(%q)", newParamName) s.Require().True(ok, "getParam(%q) result bool", newParamName) @@ -2130,20 +2125,20 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { s.Run("update and get first param", func() { var alreadyExists bool testFuncGet1 := func() { - _, alreadyExists = s.Keeper.GetParam(store, newParamName) + _, alreadyExists = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet1, "getParam(%q) on param that should not exist yet", newParamName) s.Require().True(alreadyExists, "getParam(%q) result bool on param that should not exist yet", newParamName) newParamValue = "this is an updated new param value" testFuncSet := func() { - s.Keeper.SetParam(store, newParamName, newParamValue) + s.Keeper.SetParam(s.SdkCtx, newParamName, newParamValue) } s.Require().NotPanics(testFuncSet, "setParam(%q, %q)", newParamName, newParamValue) var actual string var ok bool testFuncGet2 := func() { - actual, ok = s.Keeper.GetParam(store, newParamName) + actual, ok = s.Keeper.GetParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testFuncGet2, "getParam(%q)", newParamName) s.Require().True(ok, "getParam(%q) result bool", newParamName) @@ -2153,13 +2148,13 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { for _, name := range toDelete { s.Run("delete "+name, func() { testDeleteFunc := func() { - s.Keeper.DeleteParam(store, name) + s.Keeper.DeleteParam(s.SdkCtx, name) } s.Require().NotPanics(testDeleteFunc, "deleteParam(%q)", name) var actual string var ok bool testGetFunc := func() { - actual, ok = s.Keeper.GetParam(store, name) + actual, ok = s.Keeper.GetParam(s.SdkCtx, name) } s.Require().NotPanics(testGetFunc, "getParam(%q)", name) s.Assert().False(ok, "getParam(%q) result bool", name) @@ -2169,7 +2164,7 @@ func (s *KeeperTestSuite) TestKeeper_getSetDeleteParam() { s.Run("delete new param again", func() { testDeleteFunc := func() { - s.Keeper.DeleteParam(store, newParamName) + s.Keeper.DeleteParam(s.SdkCtx, newParamName) } s.Require().NotPanics(testDeleteFunc, "deleteParam(%q)", newParamName) }) @@ -2234,10 +2229,9 @@ func (s *KeeperTestSuite) TestKeeper_getParamAsCoinsOrDefault() { for _, tc := range tests { s.Run(tc.name, func() { if tc.setFirst { - store := s.GetStore() - s.Keeper.SetParam(store, tc.param, tc.setTo) + s.Keeper.SetParam(s.SdkCtx, tc.param, tc.setTo) defer func() { - s.Keeper.DeleteParam(store, tc.param) + s.Keeper.DeleteParam(s.SdkCtx, tc.param) }() } var actual sdk.Coins @@ -2392,3 +2386,206 @@ func (s *KeeperTestSuite) Test_toAccAddrs() { }) } } + +// TestBackwardCompatibility_KeyLayouts verifies that Collections codecs +// produce identical keys to the old KVStore approach +func (s *KeeperTestSuite) TestBackwardCompatibility_KeyLayouts() { + addr := s.addr1 + propID := uint64(42) + + s.Run("sanctioned address key", func() { + oldKey := keeper.CreateSanctionedAddrKey(addr) + + codec := sanction.AccAddressKey{} + buf := make([]byte, codec.Size(addr)) + n, err := codec.Encode(buf, addr) + s.Require().NoError(err, "encoding sanctioned address key should not error") + newKey := append([]byte{0x01}, buf[:n]...) + + s.Require().Equal(oldKey, newKey, "sanctioned address keys must match") + }) + + s.Run("temporary entry key", func() { + oldKey := keeper.CreateTemporaryKey(addr, propID) + + codec := sanction.TemporaryKeyCodec{} + pair := collections.Join(addr, propID) + buf := make([]byte, codec.Size(pair)) + n, err := codec.Encode(buf, pair) + s.Require().NoError(err, "encoding temporary entry key should not error") + newKey := append([]byte{0x02}, buf[:n]...) + + s.Require().Equal(oldKey, newKey, "temporary entry keys must match") + }) + + s.Run("proposal index key", func() { + oldKey := keeper.CreateProposalTempIndexKey(propID, addr) + codec := sanction.ProposalIndexKeyCodec{} + pair := collections.Join(propID, addr) + buf := make([]byte, codec.Size(pair)) + n, err := codec.Encode(buf, pair) + s.Require().NoError(err, "encoding proposal index key should not error") + newKey := append([]byte{0x03}, buf[:n]...) + + s.Require().Equal(oldKey, newKey, "proposal index keys must match") + }) +} + +// TestBackwardCompatibility_ReadOldData verifies that Collections can read +// data written by the old KVStore methods +func (s *KeeperTestSuite) TestBackwardCompatibility_ReadOldData() { + s.Run("read sanctioned addresses", func() { + store := s.SdkCtx.KVStore(s.App.GetKey(sanction.StoreKey)) + oldKey := keeper.CreateSanctionedAddrKey(s.addr1) + store.Set(oldKey, []byte{0x01}) + + has, err := s.Keeper.SanctionedAddressesStore.Has(s.SdkCtx, s.addr1) + s.Require().NoError(err, "checking Has() on sanctioned address must not error") + s.Require().True(has, "collections must read old sanctioned address") + + value, err := s.Keeper.SanctionedAddressesStore.Get(s.SdkCtx, s.addr1) + s.Require().NoError(err, "getting value of old sanctioned address must not error") + s.Require().Equal([]byte{0x01}, value, "sanctioned address value mismatch") + }) + + s.Run("read temporary entries", func() { + store := s.SdkCtx.KVStore(s.App.GetKey(sanction.StoreKey)) + oldKey := keeper.CreateTemporaryKey(s.addr2, uint64(10)) + store.Set(oldKey, []byte{keeper.SanctionB}) + + value, err := s.Keeper.TemporaryEntriesStore.Get(s.SdkCtx, collections.Join(s.addr2, uint64(10))) + s.Require().NoError(err, "reading old temporary entry should not error") + s.Require().Equal([]byte{keeper.SanctionB}, value, "temporary entry value mismatch") + }) + + s.Run("read proposal index", func() { + store := s.SdkCtx.KVStore(s.App.GetKey(sanction.StoreKey)) + oldKey := keeper.CreateProposalTempIndexKey(uint64(20), s.addr3) + store.Set(oldKey, []byte{keeper.UnsanctionB}) + + value, err := s.Keeper.ProposalIndex.Get(s.SdkCtx, collections.Join(uint64(20), s.addr3)) + s.Require().NoError(err, "reading old proposal index must not error") + s.Require().Equal([]byte{keeper.UnsanctionB}, value, "proposal index value mismatch") + }) +} + +// TestBackwardCompatibility_IterationWorks verifies that iteration over +// old data works correctly with Collections +func (s *KeeperTestSuite) TestBackwardCompatibility_IterationWorks() { + store := s.SdkCtx.KVStore(s.App.GetKey(sanction.StoreKey)) + + addrs := []sdk.AccAddress{s.addr1, s.addr2, s.addr3} + for _, addr := range addrs { + oldKey := keeper.CreateSanctionedAddrKey(addr) + store.Set(oldKey, []byte{0x01}) + } + + var foundAddrs []sdk.AccAddress + err := s.Keeper.SanctionedAddressesStore.Walk( + s.SdkCtx, + nil, + func(key sdk.AccAddress, value []byte) (bool, error) { + foundAddrs = append(foundAddrs, key) + return false, nil + }, + ) + + s.Require().NoError(err, "iterating over old sanctioned addresses must not error") + s.Require().Len(foundAddrs, 3, "must iterate over all old entries") + s.Require().ElementsMatch(addrs, foundAddrs, "iterated addresses must match") +} + +// TestBackwardCompatibility_ComplexScenario runs a full scenario +// that mimics real chain state +func (s *KeeperTestSuite) TestBackwardCompatibility_ComplexScenario() { + store := s.SdkCtx.KVStore(s.App.GetKey(sanction.StoreKey)) + sanctionedAddrs := []sdk.AccAddress{s.addr1, s.addr2} + + for _, addr := range sanctionedAddrs { + oldKey := keeper.CreateSanctionedAddrKey(addr) + store.Set(oldKey, []byte{0x01}) + } + + tempEntries := []struct { + addr sdk.AccAddress + propID uint64 + status byte + }{ + {s.addr3, 10, keeper.SanctionB}, + {s.addr3, 11, keeper.UnsanctionB}, + {s.addr4, 10, keeper.SanctionB}, + } + + for _, entry := range tempEntries { + store.Set(keeper.CreateTemporaryKey(entry.addr, entry.propID), []byte{entry.status}) + store.Set(keeper.CreateProposalTempIndexKey(entry.propID, entry.addr), []byte{entry.status}) + } + + s.Run("IsSanctionedAddr works with old data", func() { + s.Require().True(s.Keeper.IsSanctionedAddr(s.SdkCtx, s.addr1), "addr1 should be sanctioned") + s.Require().False(s.Keeper.IsSanctionedAddr(s.SdkCtx, s.addr3), "addr3 should be unsanctioned per latest temp") + s.Require().False(s.Keeper.IsSanctionedAddr(s.SdkCtx, s.addr5), "addr5 should not be sanctioned") + }) + + s.Run("GetAllSanctionedAddresses works", func() { + addrsStrings := s.Keeper.GetAllSanctionedAddresses(s.SdkCtx) + s.Require().Len(addrsStrings, 2, "should have 2 sanctioned addresses") + + expectedStrings := make([]string, len(sanctionedAddrs)) + for i, addr := range sanctionedAddrs { + expectedStrings[i] = addr.String() + } + s.Require().ElementsMatch(expectedStrings, addrsStrings, "sanctioned addresses mismatch") + }) + + s.Run("GetAllTemporaryEntries works", func() { + entries := s.Keeper.GetAllTemporaryEntries(s.SdkCtx) + s.Require().Len(entries, 3, "should have 3 temporary entries") + + var found bool + for _, entry := range entries { + if entry.Address == s.addr3.String() && entry.ProposalId == 10 { + found = true + s.Require().Equal( + sanction.TEMP_STATUS_SANCTIONED, + entry.Status, + "entry for addr3 prop 10 should be sanctioned", + ) + } + } + s.Require().True(found, "should find addr3 with propID 10") + }) +} + +func (s *KeeperTestSuite) TestExistingTestsWithCollections() { + s.Run("IsSanctionedAddr still works", func() { + addrUnsanctionable := sdk.AccAddress("unsanctionable_addr_") + + s.ReqOKAddPermSanct("setup", s.addr1, s.addr2, s.addr5, addrUnsanctionable) + s.ReqOKAddTempSanct(1, "setup", s.addr3, s.addr4) + s.ReqOKAddTempUnsanct(2, "setup", s.addr2, s.addr4, s.addr5) + s.ReqOKAddTempSanct(3, "setup", s.addr5) + + k := s.Keeper.WithUnsanctionableAddrs( + map[string]bool{string(addrUnsanctionable): true}, + ) + + tests := []struct { + name string + addr sdk.AccAddress + exp bool + }{ + {"sanctioned addr", s.addr1, true}, + {"sanctioned with temp unsanction", s.addr2, false}, + {"temp sanction", s.addr3, true}, + {"temp sanction then temp unsanction", s.addr4, false}, + {"sanctioned with temp unsanction then temp sanction", s.addr5, true}, + {"sanctioned addr now unsanctionable", addrUnsanctionable, false}, + } + + for _, tc := range tests { + actual := k.IsSanctionedAddr(s.SdkCtx, tc.addr) + s.Assert().Equal(tc.exp, actual, "scenario: %s", tc.name) + } + }) +} diff --git a/x/sanction/keeper/keys.go b/x/sanction/keeper/keys.go index f207ee08f8..00ed6e618e 100644 --- a/x/sanction/keeper/keys.go +++ b/x/sanction/keeper/keys.go @@ -3,6 +3,8 @@ package keeper import ( "fmt" + "cosmossdk.io/collections" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/address" "github.com/cosmos/gogoproto/proto" @@ -22,10 +24,10 @@ import ( // Proposal id temp sanction index: // - 0x03 -> 0x00 or 0x01 var ( - ParamsPrefix = []byte{0x00} - SanctionedPrefix = []byte{0x01} - TemporaryPrefix = []byte{0x02} - ProposalIndexPrefix = []byte{0x03} + ParamsPrefix = collections.NewPrefix(0) + SanctionedPrefix = collections.NewPrefix(1) + TemporaryPrefix = collections.NewPrefix(2) + ProposalIndexPrefix = collections.NewPrefix(3) ) const ( diff --git a/x/sanction/keeper/suite_test.go b/x/sanction/keeper/suite_test.go index 82f09d9e81..7569397ca2 100644 --- a/x/sanction/keeper/suite_test.go +++ b/x/sanction/keeper/suite_test.go @@ -4,12 +4,10 @@ import ( "context" "time" - "github.com/stretchr/testify/suite" - + "cosmossdk.io/core/store" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttime "github.com/cometbft/cometbft/types/time" - - storetypes "cosmossdk.io/store/types" + "github.com/stretchr/testify/suite" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -63,6 +61,7 @@ func (s *BaseTestSuite) BaseSetup() { s.StdlibCtx = context.Context(s.SdkCtx) s.GovKeeper = NewMockGovKeeper() s.Keeper = s.App.SanctionKeeper.WithGovKeeper(s.GovKeeper) + s.Require().NotNil(s.Keeper.StoreService, "StoreService must be initialized") } // AssertErrorContents calls assertions.AssertErrorContents using this suite's T. @@ -71,15 +70,16 @@ func (s *BaseTestSuite) AssertErrorContents(theError error, contains []string, m } // GetStore gets the sanction module's store. -func (s *BaseTestSuite) GetStore() storetypes.KVStore { - return s.SdkCtx.KVStore(s.Keeper.StoreKey()) +func (s *BaseTestSuite) GetStore() store.KVStore { + return s.Keeper.StoreService.OpenKVStore(s.SdkCtx) } // ClearState deletes all entries in the sanction store. func (s *BaseTestSuite) ClearState() { var keysToDelete [][]byte store := s.GetStore() - iter := store.Iterator(nil, nil) + iter, err := store.Iterator(nil, nil) + s.Require().NoError(err, "store.Iterator") closeIter := func() { if iter != nil { s.Require().NoError(iter.Close(), "iter.Close") diff --git a/x/sanction/simulation/genesis.go b/x/sanction/simulation/genesis.go index ab667c7b99..06fe71b25e 100644 --- a/x/sanction/simulation/genesis.go +++ b/x/sanction/simulation/genesis.go @@ -44,13 +44,13 @@ func RandomTempEntries(r *rand.Rand, accounts []simtypes.Account) []*sanction.Te case 0: rv = append(rv, &sanction.TemporaryEntry{ Address: acct.Address.String(), - ProposalId: uint64(r.Int63n(1000) + 1_000_000_000), //nolint:gosec // G115: safe cast from int64 to uint64 + ProposalId: uint64(r.Int63n(1000) + 1_000_000_000), //nolint:gosec // safe: value always positive and bounded Status: sanction.TEMP_STATUS_SANCTIONED, }) case 1: rv = append(rv, &sanction.TemporaryEntry{ Address: acct.Address.String(), - ProposalId: uint64(r.Int63n(1000) + 2_000_000_000), //nolint:gosec // G115: safe cast from int64 to uint64 + ProposalId: uint64(r.Int63n(1000) + 2_000_000_000), //nolint:gosec // safe: value always positive and bounded Status: sanction.TEMP_STATUS_UNSANCTIONED, }) }