package keeper import ( "time" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" "github.com/kava-labs/kava/x/auction/types" ) type Keeper struct { supplyKeeper types.SupplyKeeper storeKey sdk.StoreKey cdc *codec.Codec paramSubspace subspace.Subspace // TODO codespace } // NewKeeper returns a new auction keeper. func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper { return Keeper{ supplyKeeper: supplyKeeper, storeKey: storeKey, cdc: cdc, paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), } } // SetNextAuctionID stores an ID to be used for the next created auction func (k Keeper) SetNextAuctionID(ctx sdk.Context, id types.ID) { store := ctx.KVStore(k.storeKey) store.Set(types.NextAuctionIDKey, id.Bytes()) } // GetNextAuctionID reads the next available global ID from store // TODO might be nicer to convert not found error to a panic, it's not an error that can be recovered from func (k Keeper) GetNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.NextAuctionIDKey) if bz == nil { //return 0, types.ErrInvalidGenesis(k.codespace, "initial auction ID hasn't been set") // TODO create error return 0, sdk.ErrInternal("initial auction ID hasn't been set") } return types.NewIDFromBytes(bz), nil } // incrementNextAuctionID increments the global ID in the store by 1 func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { id, err := k.GetNextAuctionID(ctx) if err != nil { return err } k.SetNextAuctionID(ctx, id+1) return nil } // StoreNewAuction stores an auction, adding a new ID func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { newAuctionID, err := k.GetNextAuctionID(ctx) if err != nil { return 0, err } auction = auction.WithID(newAuctionID) k.SetAuction(ctx, auction) err = k.IncrementNextAuctionID(ctx) if err != nil { return 0, err } return newAuctionID, nil } // SetAuction puts the auction into the database and adds it to the queue // it overwrites any pre-existing auction with same ID func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) { // remove the auction from the byTime index if it is already in there existingAuction, found := k.GetAuction(ctx, auction.GetID()) if found { k.RemoveFromIndex(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) } // store auction store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction) store.Set(types.GetAuctionKey(auction.GetID()), bz) // add to index k.InsertIntoIndex(ctx, auction.GetEndTime(), auction.GetID()) } // getAuction gets an auction from the store by auctionID func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, bool) { var auction types.Auction store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) bz := store.Get(types.GetAuctionKey(auctionID)) if bz == nil { return auction, false } k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auction) return auction, true } // DeleteAuction removes an auction from the store without any validation func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { // remove from index auction, found := k.GetAuction(ctx, auctionID) if found { k.RemoveFromIndex(ctx, auction.GetEndTime(), auctionID) } // delete auction store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) store.Delete(types.GetAuctionKey(auctionID)) } // InsertIntoIndex adds an auction ID and end time into the byTime index func (k Keeper) InsertIntoIndex(ctx sdk.Context, endTime time.Time, auctionID types.ID) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Set(types.GetAuctionByTimeKey(endTime, auctionID), auctionID.Bytes()) } // RemoveFromIndex removes an auction ID and end time from the byTime index func (k Keeper) RemoveFromIndex(ctx sdk.Context, endTime time.Time, auctionID types.ID) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) } // IterateAuctionByTime provides an iterator over auctions ordered by auction.EndTime. // For each auction cb will be callled. If cb returns true the iterator will close and stop. // TODO can the cutoff time be removed in favour of caller specifying cutoffs in the callback? func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time.Time, cb func(auctionID types.ID) (stop bool)) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) iterator := store.Iterator( nil, // start at the very start of the prefix store sdk.PrefixEndBytes(sdk.FormatTimeBytes(inclusiveCutoffTime)), // include any keys with times equal to inclusiveCutoffTime ) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { // TODO get the auction ID - either read from store, or extract from key auctionID := types.NewIDFromBytes(iterator.Value()) if cb(auctionID) { break } } } // IterateAuctions provides an iterator over all stored auctions. // For each auction, cb will be called. If cb returns true, the iterator will close and stop. func (k Keeper) IterateAuctions(ctx sdk.Context, cb func(auction types.Auction) (stop bool)) { iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var auction types.Auction k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auction) if cb(auction) { break } } }