From d8a428e1d84caff415700a02e6235b5321e8288f Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Wed, 11 Dec 2019 22:59:06 +0000 Subject: [PATCH 01/27] rough auction type refactor --- x/auction/alias.go | 1 - x/auction/keeper/keeper.go | 288 +++++++++++++++++++++++----- x/auction/types/auctions.go | 286 +++++++++++++-------------- x/auction/types/expected_keepers.go | 17 +- 4 files changed, 381 insertions(+), 211 deletions(-) diff --git a/x/auction/alias.go b/x/auction/alias.go index 58040464..9f86f9e7 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -24,7 +24,6 @@ const ( var ( // functions aliases NewIDFromString = types.NewIDFromString - NewBaseAuction = types.NewBaseAuction NewForwardAuction = types.NewForwardAuction NewReverseAuction = types.NewReverseAuction NewForwardReverseAuction = types.NewForwardReverseAuction diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index f43f81cc..08d49a2f 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -7,11 +7,13 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/supply" + "github.com/kava-labs/kava/x/auction/types" ) type Keeper struct { - bankKeeper types.BankKeeper + supplyKeeper types.SupplyKeeper storeKey sdk.StoreKey cdc *codec.Codec paramSubspace subspace.Subspace @@ -19,23 +21,27 @@ type Keeper struct { } // NewKeeper returns a new auction keeper. -func NewKeeper(cdc *codec.Codec, bankKeeper types.BankKeeper, storeKey sdk.StoreKey, paramstore subspace.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, bankKeeper types.BankKeeper, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper { return Keeper{ - bankKeeper: bankKeeper, + supplyKeeper: supplyKeeper, storeKey: storeKey, cdc: cdc, paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), } } -// TODO these 3 start functions be combined or abstracted away? - // StartForwardAuction starts a normal auction. Known as flap in maker. -func (k Keeper) StartForwardAuction(ctx sdk.Context, seller sdk.AccAddress, lot sdk.Coin, initialBid sdk.Coin) (types.ID, sdk.Error) { +func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { // create auction - auction, initiatorOutput := types.NewForwardAuction(seller, lot, initialBid, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) - // start the auction - auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) + auction := types.NewForwardAuction(seller, lot, bidDenom, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) + + // take coins from module account + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) + if err != nil { + return 0, err + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface if err != nil { return 0, err } @@ -43,11 +49,17 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller sdk.AccAddress, lot } // StartReverseAuction starts an auction where sellers compete by offering decreasing prices. Known as flop in maker. -func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer sdk.AccAddress, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { +func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { // create auction - auction, initiatorOutput := types.NewReverseAuction(buyer, bid, initialLot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) - // start the auction - auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) + auction := types.NewReverseAuction(buyer, bid, initialLot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) + + // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. + macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) + if !macc.HasPermission(supply.Minter) { // TODO ideally don't want to import supply + return 0, sdk.ErrInternal("module does not have minting permissions") + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, &auction) if err != nil { return 0, err } @@ -55,19 +67,25 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer sdk.AccAddress, bid s } // StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. Known as flip in maker. -func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller sdk.AccAddress, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { +func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { // create auction - initialBid := sdk.NewInt64Coin(maxBid.Denom, 0) // set the bidding coin denomination from the specified max bid - auction, initiatorOutput := types.NewForwardReverseAuction(seller, lot, initialBid, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration, maxBid, otherPerson) - // start the auction - auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) + auction := types.NewForwardReverseAuction(seller, lot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration, maxBid, otherPerson) + + // take coins from module account + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.Coins{lot}) + if err != nil { + return 0, err + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, &auction) if err != nil { return 0, err } return auctionID, nil } -func (k Keeper) startAuction(ctx sdk.Context, auction types.Auction, initiatorOutput types.BankOutput) (types.ID, sdk.Error) { +// set an auction in the store, adding a new ID, and setting indexes +func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { // get ID newAuctionID, err := k.getNextAuctionID(ctx) if err != nil { @@ -76,18 +94,14 @@ func (k Keeper) startAuction(ctx sdk.Context, auction types.Auction, initiatorOu // set ID auction.SetID(newAuctionID) - // subtract coins from initiator - _, err = k.bankKeeper.SubtractCoins(ctx, initiatorOutput.Address, sdk.NewCoins(initiatorOutput.Coin)) - if err != nil { - return 0, err - } - // store auction k.SetAuction(ctx, auction) k.incrementNextAuctionID(ctx) return newAuctionID, nil } +// ============================================================================================================================== + // PlaceBid places a bid on any auction. func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { @@ -97,35 +111,179 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr return sdk.ErrInternal("auction doesn't exist") } - // place bid - coinOutputs, coinInputs, err := auction.PlaceBid(types.EndTime(ctx.BlockHeight()), bidder, lot, bid) // update auction according to what type of auction it is // TODO should this return updated Auction to be more immutable? - if err != nil { - return err + // check end time + if ctx.BlockHeight() > auction.GetEndTime() { + return sdk.ErrInternal("auction has closed") } - // TODO this will fail if someone tries to update their bid without the full bid amount sitting in their account - // sub outputs - for _, output := range coinOutputs { - _, err = k.bankKeeper.SubtractCoins(ctx, output.Address, sdk.NewCoins(output.Coin)) // TODO handle errors properly here. All coin transfers should be atomic. InputOutputCoins may work + + var err sdk.Error + var a types.Auction + switch auc := auction.(type) { + case types.ForwardAuction: + a, err = k.PlaceBidForward(ctx, auc, bidder, bid) if err != nil { - panic(err) + return err } - } - // add inputs - for _, input := range coinInputs { - _, err = k.bankKeeper.AddCoins(ctx, input.Address, sdk.NewCoins(input.Coin)) // TODO errors + case types.ReverseAuction: + a, err = k.PlaceBidReverse(ctx, auc, bidder, lot) if err != nil { - panic(err) + return err } + case types.ForwardReverseAuction: + a, err = k.PlaceBidForwardReverse(ctx, auc, bidder, bid, lot) + if err != nil { + return err + } + default: + panic("unrecognized auction type") } // store updated auction - k.SetAuction(ctx, auction) + k.SetAuction(ctx, a) // maybe move into above funcs return nil } -// CloseAuction closes an auction and distributes funds to the seller and highest bidder. -// TODO because this is called by the end blocker, it has to be valid for the duration of the EndTime block. Should maybe move this to a begin blocker? +func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) { + // Valid New Bid + if bid.Denom != a.Bid.Denom { + return a, sdk.ErrInternal("bid denom doesn't match auction") + } + if !a.Bid.IsLT(bid) { // TODO add minimum bid size + return a, sdk.ErrInternal("bid not greater than last bid") + } + + // Move Coins + increment := bid.Sub(a.Bid) + bidAmtToReturn := a.Bid + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(increment))) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(increment)) // increase in bid size is burned + if err != nil { + return a, err + } + err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(increment)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Bid = bid + // increment timeout + a.EndTime = EndTime(min(int64(ctx.BlockHeight()+types.DefaultMaxBidDuration), int64(a.MaxEndTime))) + + return a, nil +} +func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { + // Validate New Bid // TODO min bid increments, make validation code less confusing + if !a.Bid.IsEqual(a.MaxBid) { + // Auction is in forward phase, a bid here can put the auction into forward or reverse phases + if !a.Bid.IsLT(bid) { + return a, sdk.ErrInternal("auction in forward phase, new bid not higher than last bid") + } + if a.MaxBid.IsLT(bid) { + return a, sdk.ErrInternal("bid higher than max bid") + } + if lot.IsNegative() || a.Lot.IsLT(lot) { + return a, sdk.ErrInternal("lot out of bounds") + } + if lot.IsLT(a.Lot) && !bid.IsEqual(a.MaxBid) { + return a, sdk.ErrInternal("auction cannot enter reverse phase without bidding max bid") + } + } else { + // Auction is in reverse phase, it can never leave reverse phase + if !bid.IsEqual(a.MaxBid) { + return a, sdk.ErrInternal("") // not necessary + } + if lot.IsNegative() { + return a, sdk.ErrInternal("can't bid negative amount") + } + if !lot.IsLT(a.Lot) { + return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") + } + } + + // Move Coins + bidIncrement := bid.Sub(a.Bid) + bidAmtToReturn := a.Bid + lotDecrement := a.Lot.Sub(lot) + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(bidIncrement))) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(bidIncrement)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.OtherPerson, sdk.NewCoins(lotDecrement)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Lot = lot + a.Bid = bid + // increment timeout + a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) + + return types.ForwardReverseAuction{}, nil +} +func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { + // Validate New Bid + if lot.Denom != a.Lot.Denom { + return a, sdk.ErrInternal("lot denom doesn't match auction") + } + if lot.IsNegative() { + return a, sdk.ErrInternal("lot less than 0") + } + if !lot.IsLT(a.Lot) { // TODO add min bid decrements + return a, sdk.ErrInternal("lot not smaller than last lot") + } + + // Move Coins + bidAmtToReturn := a.Bid + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Lot = lot + // increment timeout + a.EndTime = EndTime(min(int64(ctx.BlockHeight()+types.DefaultMaxBidDuration), int64(a.MaxEndTime))) + + return a, nil +} + +// ========================================================================================================== + +// CloseAuction closes an auction and distributes funds to the highest bidder. func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { // get the auction from the store @@ -134,14 +292,25 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { return sdk.ErrInternal("auction doesn't exist") } // error if auction has not reached the end time - if ctx.BlockHeight() < int64(auction.GetEndTime()) { // auctions close at the end of the block with blockheight == EndTime + if ctx.BlockHeight() < int64(auction.GetEndTime()) { return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block height (%v) is under auction end time (%v)", ctx.BlockHeight(), auction.GetEndTime())) } + // payout to the last bidder - coinInput := auction.GetPayout() - _, err := k.bankKeeper.AddCoins(ctx, coinInput.Address, sdk.NewCoins(coinInput.Coin)) - if err != nil { - return err + var err sdk.Error + switch auc := auction.(type) { + case types.ForwardAuction, types.ForwardReverseAuction: + err = k.PayoutAuctionLot(ctx, auc) + if err != nil { + return err + } + case types.ReverseAuction: + err = k.MintAndPayoutAuctionLot(ctx, auc) + if err != nil { + return err + } + default: + panic("unrecognized auction type") } // Delete auction from store (and queue) @@ -149,7 +318,26 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { return nil } +func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) sdk.Error { + err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) + if err != nil { + return err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, a.Initiator, a.Bidder, sdk.NewCoins(a.Lot)) + if err != nil { + return err + } + return nil +} +func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBid(), sdk.NewCoins(a.GetLot())) + if err != nil { + return err + } + return nil +} +// ===================================================================================================================== // ---------- Store methods ---------- // Use these to add and remove auction from the store. @@ -162,7 +350,7 @@ func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { // TOD // if not found, set the id at 0 bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) store.Set(k.getNextAuctionIDKey(), bz) - // TODO Why does the gov module set the id in genesis? : + // TODO Set auction ID in genesis //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") } var auctionID types.ID @@ -177,7 +365,7 @@ func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error { bz := store.Get(k.getNextAuctionIDKey()) if bz == nil { panic("initial auctionID never set in genesis") - //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO is this needed? Why not just set it zero here? + //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO } var auctionID types.ID k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) @@ -214,7 +402,7 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, store := ctx.KVStore(k.storeKey) bz := store.Get(k.getAuctionKey(auctionID)) if bz == nil { - return auction, false // TODO what is the correct behavior when an auction is not found? gov module follows this pattern of returning a bool + return auction, false } k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auction) diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 386fb232..4193db85 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -5,26 +5,26 @@ import ( "strconv" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply" ) // Auction is an interface to several types of auction. type Auction interface { GetID() ID SetID(ID) - PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) + // PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) GetEndTime() EndTime // auctions close at the end of the block with blockheight EndTime (ie bids placed in that block are valid) - GetPayout() BankInput - String() string + // GetPayout() BankInput } // BaseAuction type shared by all Auctions type BaseAuction struct { ID ID - Initiator sdk.AccAddress // Person who starts the auction. Giving away Lot (aka seller in a forward auction) + Initiator string // Module who starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) - EndTime EndTime // Block height at which the auction closes. It closes at the end of this block + EndTime EndTime // Block height at which the auction closes. It closes at the end of this block // TODO change to time type MaxEndTime EndTime // Maximum closing time. Auctions can close before this but never after. } @@ -56,48 +56,30 @@ type BankOutput struct { } // GetID getter for auction ID -func (a BaseAuction) GetID() ID { return a.ID } +func (a *BaseAuction) GetID() ID { return a.ID } // SetID setter for auction ID func (a *BaseAuction) SetID(id ID) { a.ID = id } +// GetBid getter for auction bid +func (a *BaseAuction) GetBid() sdk.Coin { return a.Bid } + +// GetLot getter for auction lot +func (a *BaseAuction) GetLot() sdk.Coin { return a.Lot } + // GetEndTime getter for auction end time -func (a BaseAuction) GetEndTime() EndTime { return a.EndTime } +func (a *BaseAuction) GetEndTime() EndTime { return a.EndTime } // GetPayout implements Auction -func (a BaseAuction) GetPayout() BankInput { - return BankInput{a.Bidder, a.Lot} -} - -// PlaceBid implements Auction -func (a *BaseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { - // TODO check lot size matches lot? - // check auction has not closed - if currentBlockHeight > a.EndTime { - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") - } - // check bid is greater than last bid - if !a.Bid.IsLT(bid) { // TODO add minimum bid size - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") - } - // calculate coin movements - outputs := []BankOutput{{bidder, bid}} // new bidder pays bid now - inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller - - // update auction - a.Bidder = bidder - a.Bid = bid - // increment timeout // TODO into keeper? - a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - - return outputs, inputs, nil -} +// func (a BaseAuction) GetPayout() BankInput { +// return BankInput{a.Bidder, a.Lot} +// } func (e EndTime) String() string { return string(e) } -func (a BaseAuction) String() string { +func (a *BaseAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s Lot: %s @@ -111,118 +93,108 @@ func (a BaseAuction) String() string { ) } -// NewBaseAuction creates a new base auction -func NewBaseAuction(seller sdk.AccAddress, lot sdk.Coin, initialBid sdk.Coin, EndTime EndTime) BaseAuction { - auction := BaseAuction{ - // no ID - Initiator: seller, - Lot: lot, - Bidder: seller, // send the proceeds from the first bid back to the seller - Bid: initialBid, // set this to zero most of the time - EndTime: EndTime, - MaxEndTime: EndTime, - } - return auction -} - // ForwardAuction type for forward auctions type ForwardAuction struct { - BaseAuction + *BaseAuction } // NewForwardAuction creates a new forward auction -func NewForwardAuction(seller sdk.AccAddress, lot sdk.Coin, initialBid sdk.Coin, EndTime EndTime) (ForwardAuction, BankOutput) { - auction := ForwardAuction{BaseAuction{ +func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime EndTime) ForwardAuction { + auction := ForwardAuction{&BaseAuction{ // no ID Initiator: seller, Lot: lot, - Bidder: seller, // send the proceeds from the first bid back to the seller - Bid: initialBid, // set this to zero most of the time + Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided + Bid: sdk.NewInt64Coin(bidDenom, 0), EndTime: EndTime, MaxEndTime: EndTime, }} - output := BankOutput{seller, lot} - return auction, output + // output := BankOutput{seller, lot} + return auction } // PlaceBid implements Auction -func (a *ForwardAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { - // TODO check lot size matches lot? - // check auction has not closed - if currentBlockHeight > a.EndTime { - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") - } - // check bid is greater than last bid - if !a.Bid.IsLT(bid) { // TODO add minimum bid size - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") - } - // calculate coin movements - outputs := []BankOutput{{bidder, bid}} // new bidder pays bid now - inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller +// func (a *ForwardAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { +// // TODO check lot size matches lot? +// // check auction has not closed +// if currentBlockHeight > a.EndTime { +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") +// } +// // check bid is greater than last bid +// if !a.Bid.IsLT(bid) { // TODO add minimum bid size +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") +// } +// // calculate coin movements +// outputs := []BankOutput{{bidder, bid}} // new bidder pays bid now +// inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller - // update auction - a.Bidder = bidder - a.Bid = bid - // increment timeout // TODO into keeper? - a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? +// // update auction +// a.Bidder = bidder +// a.Bid = bid +// // increment timeout // TODO into keeper? +// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - return outputs, inputs, nil -} +// return outputs, inputs, nil +// } // ReverseAuction type for reverse auctions // TODO when exporting state and initializing a new genesis, we'll need a way to differentiate forward from reverse auctions type ReverseAuction struct { - BaseAuction + *BaseAuction } // NewReverseAuction creates a new reverse auction -func NewReverseAuction(buyer sdk.AccAddress, bid sdk.Coin, initialLot sdk.Coin, EndTime EndTime) (ReverseAuction, BankOutput) { - auction := ReverseAuction{BaseAuction{ +func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime EndTime) ReverseAuction { + // Bidder set here receives the proceeds from the first bid placed. This is set to the address of the module account. + // When this happens it uses supply.SendCoinsFromModuleToAccount, rather than SendCoinsFromModuleToModule. + // Currently not a problem but if extra checks are added to module accounts this will skip them. + // TODO description + auction := ReverseAuction{&BaseAuction{ // no ID - Initiator: buyer, + Initiator: buyerModAccName, Lot: initialLot, - Bidder: buyer, // send proceeds from the first bid to the buyer - Bid: bid, // amount that the buyer it buying - doesn't change over course of auction + Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. + Bid: bid, // amount that the buyer it buying - doesn't change over course of auction EndTime: EndTime, MaxEndTime: EndTime, }} - output := BankOutput{buyer, initialLot} - return auction, output + //output := BankOutput{buyer, initialLot} + return auction } // PlaceBid implements Auction -func (a *ReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { +// func (a *ReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { - // check bid size matches bid? - // check auction has not closed - if currentBlockHeight > a.EndTime { - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") - } - // check bid is less than last bid - if !lot.IsLT(a.Lot) { // TODO add min bid decrements - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") - } - // calculate coin movements - outputs := []BankOutput{{bidder, a.Bid}} // new bidder pays bid now - inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to buyer +// // check bid size matches bid? +// // check auction has not closed +// if currentBlockHeight > a.EndTime { +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") +// } +// // check bid is less than last bid +// if !lot.IsLT(a.Lot) { // TODO add min bid decrements +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") +// } +// // calculate coin movements +// outputs := []BankOutput{{bidder, a.Bid}} // new bidder pays bid now +// inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to buyer - // update auction - a.Bidder = bidder - a.Lot = lot - // increment timeout // TODO into keeper? - a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? +// // update auction +// a.Bidder = bidder +// a.Lot = lot +// // increment timeout // TODO into keeper? +// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - return outputs, inputs, nil -} +// return outputs, inputs, nil +// } // ForwardReverseAuction type for forward reverse auction type ForwardReverseAuction struct { - BaseAuction + *BaseAuction MaxBid sdk.Coin - OtherPerson sdk.AccAddress // TODO rename, this is normally the original CDP owner + OtherPerson sdk.AccAddress // TODO rename, this is normally the original CDP owner, will have to be updated to account for deposits } -func (a ForwardReverseAuction) String() string { +func (a *ForwardReverseAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s Lot: %s @@ -239,69 +211,69 @@ func (a ForwardReverseAuction) String() string { } // NewForwardReverseAuction creates a new forward reverse auction -func NewForwardReverseAuction(seller sdk.AccAddress, lot sdk.Coin, initialBid sdk.Coin, EndTime EndTime, maxBid sdk.Coin, otherPerson sdk.AccAddress) (ForwardReverseAuction, BankOutput) { +func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime EndTime, maxBid sdk.Coin, otherPerson sdk.AccAddress) ForwardReverseAuction { auction := ForwardReverseAuction{ - BaseAuction: BaseAuction{ + BaseAuction: &BaseAuction{ // no ID Initiator: seller, Lot: lot, - Bidder: seller, // send the proceeds from the first bid back to the seller - Bid: initialBid, // 0 most of the time + Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided + Bid: sdk.NewInt64Coin(maxBid.Denom, 0), EndTime: EndTime, MaxEndTime: EndTime}, MaxBid: maxBid, OtherPerson: otherPerson, } - output := BankOutput{seller, lot} - return auction, output + //output := BankOutput{seller, lot} + return auction } // PlaceBid implements auction -func (a *ForwardReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) (outputs []BankOutput, inputs []BankInput, err sdk.Error) { - // check auction has not closed - if currentBlockHeight > a.EndTime { - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") - } +// func (a *ForwardReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) (outputs []BankOutput, inputs []BankInput, err sdk.Error) { +// // check auction has not closed +// if currentBlockHeight > a.EndTime { +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") +// } - // determine phase of auction - switch { - case a.Bid.IsLT(a.MaxBid) && bid.IsLT(a.MaxBid): - // Forward auction phase - if !a.Bid.IsLT(bid) { // TODO add min bid increments - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") - } - outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now - inputs = []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller - case a.Bid.IsLT(a.MaxBid): - // Switch over phase - if !bid.IsEqual(a.MaxBid) { // require bid == a.MaxBid - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid greater than the max bid") - } - outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now - inputs = []BankInput{ - {a.Bidder, a.Bid}, // old bidder is paid back - {a.Initiator, bid.Sub(a.Bid)}, // extra goes to seller - {a.OtherPerson, a.Lot.Sub(lot)}, //decrease in price for goes to original CDP owner - } +// // determine phase of auction +// switch { +// case a.Bid.IsLT(a.MaxBid) && bid.IsLT(a.MaxBid): +// // Forward auction phase +// if !a.Bid.IsLT(bid) { // TODO add min bid increments +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") +// } +// outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now +// inputs = []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller +// case a.Bid.IsLT(a.MaxBid): +// // Switch over phase +// if !bid.IsEqual(a.MaxBid) { // require bid == a.MaxBid +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid greater than the max bid") +// } +// outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now +// inputs = []BankInput{ +// {a.Bidder, a.Bid}, // old bidder is paid back +// {a.Initiator, bid.Sub(a.Bid)}, // extra goes to seller +// {a.OtherPerson, a.Lot.Sub(lot)}, //decrease in price for goes to original CDP owner +// } - case a.Bid.IsEqual(a.MaxBid): - // Reverse auction phase - if !lot.IsLT(a.Lot) { // TODO add min bid decrements - return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") - } - outputs = []BankOutput{{bidder, a.Bid}} // new bidder pays bid now - inputs = []BankInput{{a.Bidder, a.Bid}, {a.OtherPerson, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to original CDP owner - default: - panic("should never be reached") // TODO - } +// case a.Bid.IsEqual(a.MaxBid): +// // Reverse auction phase +// if !lot.IsLT(a.Lot) { // TODO add min bid decrements +// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") +// } +// outputs = []BankOutput{{bidder, a.Bid}} // new bidder pays bid now +// inputs = []BankInput{{a.Bidder, a.Bid}, {a.OtherPerson, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to original CDP owner +// default: +// panic("should never be reached") // TODO +// } - // update auction - a.Bidder = bidder - a.Lot = lot - a.Bid = bid - // increment timeout - // TODO use bid duration param - a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? +// // update auction +// a.Bidder = bidder +// a.Lot = lot +// a.Bid = bid +// // increment timeout +// // TODO use bid duration param +// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - return outputs, inputs, nil -} +// return outputs, inputs, nil +// } diff --git a/x/auction/types/expected_keepers.go b/x/auction/types/expected_keepers.go index 5a99550d..85956576 100644 --- a/x/auction/types/expected_keepers.go +++ b/x/auction/types/expected_keepers.go @@ -2,9 +2,20 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" ) -type BankKeeper interface { - SubtractCoins(sdk.Context, sdk.AccAddress, sdk.Coins) (sdk.Coins, sdk.Error) - AddCoins(sdk.Context, sdk.AccAddress, sdk.Coins) (sdk.Coins, sdk.Error) +// SupplyKeeper defines the expected supply Keeper +type SupplyKeeper interface { + //GetSupply(ctx sdk.Context) supplyexported.SupplyI + + //GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI + + SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error + + BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error } From 231aa75774fd512340d9ac5b794a7b4e7f8f3427 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 12 Dec 2019 00:02:06 +0000 Subject: [PATCH 02/27] replace endTime type --- x/auction/keeper/keeper.go | 44 +++++------ x/auction/types/auctions.go | 154 ++++-------------------------------- x/auction/types/params.go | 27 +++---- 3 files changed, 46 insertions(+), 179 deletions(-) diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index 08d49a2f..acf65cb1 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -3,6 +3,7 @@ package keeper import ( "bytes" "fmt" + "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,7 +22,7 @@ type Keeper struct { } // NewKeeper returns a new auction keeper. -func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, bankKeeper types.BankKeeper, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper { return Keeper{ supplyKeeper: supplyKeeper, storeKey: storeKey, @@ -33,7 +34,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, bankKeeper types.BankKee // StartForwardAuction starts a normal auction. Known as flap in maker. func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { // create auction - auction := types.NewForwardAuction(seller, lot, bidDenom, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) + auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) // take coins from module account err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) @@ -41,7 +42,7 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin return 0, err } // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface + auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface? if err != nil { return 0, err } @@ -51,7 +52,7 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin // StartReverseAuction starts an auction where sellers compete by offering decreasing prices. Known as flop in maker. func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { // create auction - auction := types.NewReverseAuction(buyer, bid, initialLot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) + auction := types.NewReverseAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) @@ -69,7 +70,7 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, // StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. Known as flip in maker. func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { // create auction - auction := types.NewForwardReverseAuction(seller, lot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration, maxBid, otherPerson) + auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, otherPerson) // take coins from module account err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.Coins{lot}) @@ -100,8 +101,6 @@ func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.I return newAuctionID, nil } -// ============================================================================================================================== - // PlaceBid places a bid on any auction. func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { @@ -112,7 +111,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr } // check end time - if ctx.BlockHeight() > auction.GetEndTime() { + if ctx.BlockTime().After(auction.GetEndTime()) { return sdk.ErrInternal("auction has closed") } @@ -139,7 +138,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr } // store updated auction - k.SetAuction(ctx, a) // maybe move into above funcs + k.SetAuction(ctx, a) // TODO maybe move into above funcs return nil } @@ -180,7 +179,7 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder a.Bidder = bidder a.Bid = bid // increment timeout - a.EndTime = EndTime(min(int64(ctx.BlockHeight()+types.DefaultMaxBidDuration), int64(a.MaxEndTime))) + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) // TODO write a min func for time types return a, nil } @@ -242,7 +241,7 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu a.Lot = lot a.Bid = bid // increment timeout - a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) return types.ForwardReverseAuction{}, nil } @@ -276,13 +275,11 @@ func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder a.Bidder = bidder a.Lot = lot // increment timeout - a.EndTime = EndTime(min(int64(ctx.BlockHeight()+types.DefaultMaxBidDuration), int64(a.MaxEndTime))) + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) return a, nil } -// ========================================================================================================== - // CloseAuction closes an auction and distributes funds to the highest bidder. func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { @@ -292,8 +289,8 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { return sdk.ErrInternal("auction doesn't exist") } // error if auction has not reached the end time - if ctx.BlockHeight() < int64(auction.GetEndTime()) { - return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block height (%v) is under auction end time (%v)", ctx.BlockHeight(), auction.GetEndTime())) + if ctx.BlockTime().Before(auction.GetEndTime()) { + return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block time (%v) is under auction end time (%v)", ctx.BlockTime(), auction.GetEndTime())) } // payout to the last bidder @@ -330,19 +327,18 @@ func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) return nil } func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBid(), sdk.NewCoins(a.GetLot())) + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBidder(), sdk.NewCoins(a.GetLot())) if err != nil { return err } return nil } -// ===================================================================================================================== // ---------- Store methods ---------- // Use these to add and remove auction from the store. // getNextAuctionID gets the next available global AuctionID -func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { // TODO don't need error return here +func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { // get next ID from store store := ctx.KVStore(k.storeKey) bz := store.Get(k.getNextAuctionIDKey()) @@ -433,7 +429,7 @@ func (k Keeper) getAuctionKey(auctionID types.ID) []byte { } // Inserts a AuctionID into the queue at endTime -func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime types.EndTime, auctionID types.ID) { +func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { // get the store store := ctx.KVStore(k.storeKey) // marshal thing to be inserted @@ -446,13 +442,13 @@ func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime types.EndTime, auctionI } // removes an auctionID from the queue -func (k Keeper) removeFromQueue(ctx sdk.Context, endTime types.EndTime, auctionID types.ID) { +func (k Keeper) removeFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { store := ctx.KVStore(k.storeKey) store.Delete(getQueueElementKey(endTime, auctionID)) } // Returns an iterator for all the auctions in the queue that expire by endTime -func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime types.EndTime) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? +func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? // get store store := ctx.KVStore(k.storeKey) // get an interator @@ -472,7 +468,7 @@ var queueKeyPrefix = []byte("queue") var keyDelimiter = []byte(":") // Returns half a key for an auctionID in the queue, it missed the id off the end -func getQueueElementKeyPrefix(endTime types.EndTime) []byte { +func getQueueElementKeyPrefix(endTime time.Time) []byte { return bytes.Join([][]byte{ queueKeyPrefix, sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering @@ -480,7 +476,7 @@ func getQueueElementKeyPrefix(endTime types.EndTime) []byte { } // Returns the key for an auctionID in the queue -func getQueueElementKey(endTime types.EndTime, auctionID types.ID) []byte { +func getQueueElementKey(endTime time.Time, auctionID types.ID) []byte { return bytes.Join([][]byte{ queueKeyPrefix, sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 4193db85..aa1e6e96 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -3,6 +3,7 @@ package types import ( "fmt" "strconv" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/supply" @@ -12,9 +13,9 @@ import ( type Auction interface { GetID() ID SetID(ID) - // PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) - GetEndTime() EndTime // auctions close at the end of the block with blockheight EndTime (ie bids placed in that block are valid) - // GetPayout() BankInput + GetBidder() sdk.AccAddress + GetLot() sdk.Coin + GetEndTime() time.Time } // BaseAuction type shared by all Auctions @@ -24,8 +25,8 @@ type BaseAuction struct { Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) - EndTime EndTime // Block height at which the auction closes. It closes at the end of this block // TODO change to time type - MaxEndTime EndTime // Maximum closing time. Auctions can close before this but never after. + EndTime time.Time // Auction closing time. Triggers at the end of the block with time ≥ endTime (bids placed in that block are valid) // TODO ensure everything is consistent with this + MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } // ID type for auction IDs @@ -40,21 +41,6 @@ func NewIDFromString(s string) (ID, error) { return ID(n), nil } -// EndTime type for end time of auctions -type EndTime int64 // TODO rename to Blockheight or don't define custom type - -// BankInput the input and output types from the bank module where used here. But they use sdk.Coins instad of sdk.Coin. So it caused a lot of type conversion as auction mainly uses sdk.Coin. -type BankInput struct { - Address sdk.AccAddress - Coin sdk.Coin -} - -// BankOutput output type for auction bids -type BankOutput struct { - Address sdk.AccAddress - Coin sdk.Coin -} - // GetID getter for auction ID func (a *BaseAuction) GetID() ID { return a.ID } @@ -62,22 +48,13 @@ func (a *BaseAuction) GetID() ID { return a.ID } func (a *BaseAuction) SetID(id ID) { a.ID = id } // GetBid getter for auction bid -func (a *BaseAuction) GetBid() sdk.Coin { return a.Bid } +func (a *BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } // GetLot getter for auction lot func (a *BaseAuction) GetLot() sdk.Coin { return a.Lot } // GetEndTime getter for auction end time -func (a *BaseAuction) GetEndTime() EndTime { return a.EndTime } - -// GetPayout implements Auction -// func (a BaseAuction) GetPayout() BankInput { -// return BankInput{a.Bidder, a.Lot} -// } - -func (e EndTime) String() string { - return string(e) -} +func (a *BaseAuction) GetEndTime() time.Time { return a.EndTime } func (a *BaseAuction) String() string { return fmt.Sprintf(`Auction %d: @@ -99,7 +76,7 @@ type ForwardAuction struct { } // NewForwardAuction creates a new forward auction -func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime EndTime) ForwardAuction { +func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime time.Time) ForwardAuction { auction := ForwardAuction{&BaseAuction{ // no ID Initiator: seller, @@ -113,42 +90,17 @@ func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime End return auction } -// PlaceBid implements Auction -// func (a *ForwardAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { -// // TODO check lot size matches lot? -// // check auction has not closed -// if currentBlockHeight > a.EndTime { -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") -// } -// // check bid is greater than last bid -// if !a.Bid.IsLT(bid) { // TODO add minimum bid size -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") -// } -// // calculate coin movements -// outputs := []BankOutput{{bidder, bid}} // new bidder pays bid now -// inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller - -// // update auction -// a.Bidder = bidder -// a.Bid = bid -// // increment timeout // TODO into keeper? -// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - -// return outputs, inputs, nil -// } - // ReverseAuction type for reverse auctions -// TODO when exporting state and initializing a new genesis, we'll need a way to differentiate forward from reverse auctions type ReverseAuction struct { *BaseAuction } // NewReverseAuction creates a new reverse auction -func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime EndTime) ReverseAuction { - // Bidder set here receives the proceeds from the first bid placed. This is set to the address of the module account. - // When this happens it uses supply.SendCoinsFromModuleToAccount, rather than SendCoinsFromModuleToModule. - // Currently not a problem but if extra checks are added to module accounts this will skip them. - // TODO description +func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) ReverseAuction { + // TODO setting the bidder here is a bit hacky + // Needs to be set so that when the first bid is placed, it is paid out to the initiator. + // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToModule. Not a problem currently but if checks/logic regarding modules accounts where added to those methods they would be bypassed. + // Alternative: set address to nil, and catch it in an if statement in place bid auction := ReverseAuction{&BaseAuction{ // no ID Initiator: buyerModAccName, @@ -158,35 +110,9 @@ func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin EndTime: EndTime, MaxEndTime: EndTime, }} - //output := BankOutput{buyer, initialLot} return auction } -// PlaceBid implements Auction -// func (a *ReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { - -// // check bid size matches bid? -// // check auction has not closed -// if currentBlockHeight > a.EndTime { -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") -// } -// // check bid is less than last bid -// if !lot.IsLT(a.Lot) { // TODO add min bid decrements -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") -// } -// // calculate coin movements -// outputs := []BankOutput{{bidder, a.Bid}} // new bidder pays bid now -// inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to buyer - -// // update auction -// a.Bidder = bidder -// a.Lot = lot -// // increment timeout // TODO into keeper? -// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - -// return outputs, inputs, nil -// } - // ForwardReverseAuction type for forward reverse auction type ForwardReverseAuction struct { *BaseAuction @@ -211,7 +137,7 @@ func (a *ForwardReverseAuction) String() string { } // NewForwardReverseAuction creates a new forward reverse auction -func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime EndTime, maxBid sdk.Coin, otherPerson sdk.AccAddress) ForwardReverseAuction { +func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, otherPerson sdk.AccAddress) ForwardReverseAuction { auction := ForwardReverseAuction{ BaseAuction: &BaseAuction{ // no ID @@ -227,53 +153,3 @@ func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime EndTime, maxB //output := BankOutput{seller, lot} return auction } - -// PlaceBid implements auction -// func (a *ForwardReverseAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) (outputs []BankOutput, inputs []BankInput, err sdk.Error) { -// // check auction has not closed -// if currentBlockHeight > a.EndTime { -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") -// } - -// // determine phase of auction -// switch { -// case a.Bid.IsLT(a.MaxBid) && bid.IsLT(a.MaxBid): -// // Forward auction phase -// if !a.Bid.IsLT(bid) { // TODO add min bid increments -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") -// } -// outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now -// inputs = []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller -// case a.Bid.IsLT(a.MaxBid): -// // Switch over phase -// if !bid.IsEqual(a.MaxBid) { // require bid == a.MaxBid -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid greater than the max bid") -// } -// outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now -// inputs = []BankInput{ -// {a.Bidder, a.Bid}, // old bidder is paid back -// {a.Initiator, bid.Sub(a.Bid)}, // extra goes to seller -// {a.OtherPerson, a.Lot.Sub(lot)}, //decrease in price for goes to original CDP owner -// } - -// case a.Bid.IsEqual(a.MaxBid): -// // Reverse auction phase -// if !lot.IsLT(a.Lot) { // TODO add min bid decrements -// return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") -// } -// outputs = []BankOutput{{bidder, a.Bid}} // new bidder pays bid now -// inputs = []BankInput{{a.Bidder, a.Bid}, {a.OtherPerson, a.Lot.Sub(lot)}} // old bidder is paid back, decrease in price for goes to original CDP owner -// default: -// panic("should never be reached") // TODO -// } - -// // update auction -// a.Bidder = bidder -// a.Lot = lot -// a.Bid = bid -// // increment timeout -// // TODO use bid duration param -// a.EndTime = EndTime(min(int64(currentBlockHeight+DefaultMaxBidDuration), int64(a.MaxEndTime))) // TODO is there a better way to structure these types? - -// return outputs, inputs, nil -// } diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 527cd844..af189ed2 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -3,16 +3,17 @@ package types import ( "bytes" "fmt" + "time" "github.com/cosmos/cosmos-sdk/x/params/subspace" ) // Defaults for auction params const ( - // DefaultMaxAuctionDuration max length of auction, roughly 2 days in blocks - DefaultMaxAuctionDuration EndTime = 2 * 24 * 3600 / 5 + // DefaultMaxAuctionDuration max length of auction + DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour // DefaultBidDuration how long an auction gets extended when someone bids, roughly 3 hours in blocks - DefaultMaxBidDuration EndTime = 3 * 3600 / 5 + DefaultMaxBidDuration time.Duration = 3 * time.Hour // DefaultStartingAuctionID what the id of the first auction will be DefaultStartingAuctionID ID = ID(0) ) @@ -21,24 +22,24 @@ const ( var ( // ParamStoreKeyAuctionParams Param store key for auction params KeyAuctionBidDuration = []byte("MaxBidDuration") - KeyAuctionDuration = []byte("MaxAuctionDuration") - KeyAuctionStartingID = []byte("StartingAuctionID") + KeyAuctionDuration = []byte("MaxAuctionDuration") + KeyAuctionStartingID = []byte("StartingAuctionID") ) var _ subspace.ParamSet = &AuctionParams{} // AuctionParams governance parameters for auction module type AuctionParams struct { - MaxAuctionDuration EndTime `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction, in blocks - MaxBidDuration EndTime `json:"max_bid_duration" yaml:"max_bid_duration"` - StartingAuctionID ID `json:"starting_auction_id" yaml:"starting_auction_id"` + MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction, in blocks + MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` + StartingAuctionID ID `json:"starting_auction_id" yaml:"starting_auction_id"` } // NewAuctionParams creates a new AuctionParams object -func NewAuctionParams(maxAuctionDuration EndTime, bidDuration EndTime, startingID ID) AuctionParams { +func NewAuctionParams(maxAuctionDuration time.Duration, bidDuration time.Duration, startingID ID) AuctionParams { return AuctionParams{ MaxAuctionDuration: maxAuctionDuration, - MaxBidDuration: bidDuration, + MaxBidDuration: bidDuration, StartingAuctionID: startingID, } } @@ -85,12 +86,6 @@ func (ap AuctionParams) String() string { // Validate checks that the parameters have valid values. func (ap AuctionParams) Validate() error { - if ap.MaxAuctionDuration <= EndTime(0) { - return fmt.Errorf("max auction duration should be positive, is %s", ap.MaxAuctionDuration) - } - if ap.MaxBidDuration <= EndTime(0) { - return fmt.Errorf("bid duration should be positive, is %s", ap.MaxBidDuration) - } if ap.StartingAuctionID <= ID(0) { return fmt.Errorf("starting auction ID should be positive, is %v", ap.StartingAuctionID) } From 5618e11990aced27109b9e15c6083faab2a82cf8 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 12 Dec 2019 00:16:10 +0000 Subject: [PATCH 03/27] split keeper file up --- x/auction/keeper/auctions.go | 297 ++++++++++++++++++++++ x/auction/keeper/keeper.go | 471 ----------------------------------- x/auction/keeper/store.go | 187 ++++++++++++++ 3 files changed, 484 insertions(+), 471 deletions(-) create mode 100644 x/auction/keeper/auctions.go create mode 100644 x/auction/keeper/store.go diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go new file mode 100644 index 00000000..9a5630d6 --- /dev/null +++ b/x/auction/keeper/auctions.go @@ -0,0 +1,297 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply" + "github.com/kava-labs/kava/x/auction/types" +) + +// StartForwardAuction starts a normal auction. +func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { + // create auction + auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) + + // take coins from module account + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) + if err != nil { + return 0, err + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface? + if err != nil { + return 0, err + } + return auctionID, nil +} + +// StartReverseAuction starts an auction where sellers compete by offering decreasing prices. +func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { + // create auction + auction := types.NewReverseAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) + + // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. + macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) + if !macc.HasPermission(supply.Minter) { // TODO ideally don't want to import supply + return 0, sdk.ErrInternal("module does not have minting permissions") + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, &auction) + if err != nil { + return 0, err + } + return auctionID, nil +} + +// StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. +func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { + // create auction + auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, otherPerson) + + // take coins from module account + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.Coins{lot}) + if err != nil { + return 0, err + } + // store the auction + auctionID, err := k.storeNewAuction(ctx, &auction) + if err != nil { + return 0, err + } + return auctionID, nil +} + +// PlaceBid places a bid on any auction. +func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { + + // get auction from store + auction, found := k.GetAuction(ctx, auctionID) + if !found { + return sdk.ErrInternal("auction doesn't exist") + } + + // check end time + if ctx.BlockTime().After(auction.GetEndTime()) { + return sdk.ErrInternal("auction has closed") + } + + var err sdk.Error + var a types.Auction + switch auc := auction.(type) { + case types.ForwardAuction: + a, err = k.PlaceBidForward(ctx, auc, bidder, bid) + if err != nil { + return err + } + case types.ReverseAuction: + a, err = k.PlaceBidReverse(ctx, auc, bidder, lot) + if err != nil { + return err + } + case types.ForwardReverseAuction: + a, err = k.PlaceBidForwardReverse(ctx, auc, bidder, bid, lot) + if err != nil { + return err + } + default: + panic("unrecognized auction type") + } + + // store updated auction + k.SetAuction(ctx, a) // TODO maybe move into above funcs + + return nil +} + +func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) { + // Valid New Bid + if bid.Denom != a.Bid.Denom { + return a, sdk.ErrInternal("bid denom doesn't match auction") + } + if !a.Bid.IsLT(bid) { // TODO add minimum bid size + return a, sdk.ErrInternal("bid not greater than last bid") + } + + // Move Coins + increment := bid.Sub(a.Bid) + bidAmtToReturn := a.Bid + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(increment))) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(increment)) // increase in bid size is burned + if err != nil { + return a, err + } + err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(increment)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Bid = bid + // increment timeout + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) // TODO write a min func for time types + + return a, nil +} +func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { + // Validate New Bid // TODO min bid increments, make validation code less confusing + if !a.Bid.IsEqual(a.MaxBid) { + // Auction is in forward phase, a bid here can put the auction into forward or reverse phases + if !a.Bid.IsLT(bid) { + return a, sdk.ErrInternal("auction in forward phase, new bid not higher than last bid") + } + if a.MaxBid.IsLT(bid) { + return a, sdk.ErrInternal("bid higher than max bid") + } + if lot.IsNegative() || a.Lot.IsLT(lot) { + return a, sdk.ErrInternal("lot out of bounds") + } + if lot.IsLT(a.Lot) && !bid.IsEqual(a.MaxBid) { + return a, sdk.ErrInternal("auction cannot enter reverse phase without bidding max bid") + } + } else { + // Auction is in reverse phase, it can never leave reverse phase + if !bid.IsEqual(a.MaxBid) { + return a, sdk.ErrInternal("") // not necessary + } + if lot.IsNegative() { + return a, sdk.ErrInternal("can't bid negative amount") + } + if !lot.IsLT(a.Lot) { + return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") + } + } + + // Move Coins + bidIncrement := bid.Sub(a.Bid) + bidAmtToReturn := a.Bid + lotDecrement := a.Lot.Sub(lot) + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(bidIncrement))) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(bidIncrement)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.OtherPerson, sdk.NewCoins(lotDecrement)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Lot = lot + a.Bid = bid + // increment timeout + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) + + return types.ForwardReverseAuction{}, nil +} +func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { + // Validate New Bid + if lot.Denom != a.Lot.Denom { + return a, sdk.ErrInternal("lot denom doesn't match auction") + } + if lot.IsNegative() { + return a, sdk.ErrInternal("lot less than 0") + } + if !lot.IsLT(a.Lot) { // TODO add min bid decrements + return a, sdk.ErrInternal("lot not smaller than last lot") + } + + // Move Coins + bidAmtToReturn := a.Bid + if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + } + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Lot = lot + // increment timeout + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) + + return a, nil +} + +// CloseAuction closes an auction and distributes funds to the highest bidder. +func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { + + // get the auction from the store + auction, found := k.GetAuction(ctx, auctionID) + if !found { + return sdk.ErrInternal("auction doesn't exist") + } + // error if auction has not reached the end time + if ctx.BlockTime().Before(auction.GetEndTime()) { + return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block time (%v) is under auction end time (%v)", ctx.BlockTime(), auction.GetEndTime())) + } + + // payout to the last bidder + var err sdk.Error + switch auc := auction.(type) { + case types.ForwardAuction, types.ForwardReverseAuction: + err = k.PayoutAuctionLot(ctx, auc) + if err != nil { + return err + } + case types.ReverseAuction: + err = k.MintAndPayoutAuctionLot(ctx, auc) + if err != nil { + return err + } + default: + panic("unrecognized auction type") + } + + // Delete auction from store (and queue) + k.DeleteAuction(ctx, auctionID) + + return nil +} +func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) sdk.Error { + err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) + if err != nil { + return err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, a.Initiator, a.Bidder, sdk.NewCoins(a.Lot)) + if err != nil { + return err + } + return nil +} +func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { + // TODO this function is responsible for the addition of GetBidder and GetLot to auction interface. Could be split in to two funcs that operate on concrete auction types + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBidder(), sdk.NewCoins(a.GetLot())) + if err != nil { + return err + } + return nil +} diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index acf65cb1..fe4cd04a 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -1,14 +1,9 @@ package keeper import ( - "bytes" - "fmt" - "time" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" - "github.com/cosmos/cosmos-sdk/x/supply" "github.com/kava-labs/kava/x/auction/types" ) @@ -30,469 +25,3 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), } } - -// StartForwardAuction starts a normal auction. Known as flap in maker. -func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { - // create auction - auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) - - // take coins from module account - err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) - if err != nil { - return 0, err - } - // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface? - if err != nil { - return 0, err - } - return auctionID, nil -} - -// StartReverseAuction starts an auction where sellers compete by offering decreasing prices. Known as flop in maker. -func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { - // create auction - auction := types.NewReverseAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) - - // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. - macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) - if !macc.HasPermission(supply.Minter) { // TODO ideally don't want to import supply - return 0, sdk.ErrInternal("module does not have minting permissions") - } - // store the auction - auctionID, err := k.storeNewAuction(ctx, &auction) - if err != nil { - return 0, err - } - return auctionID, nil -} - -// StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. Known as flip in maker. -func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { - // create auction - auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, otherPerson) - - // take coins from module account - err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.Coins{lot}) - if err != nil { - return 0, err - } - // store the auction - auctionID, err := k.storeNewAuction(ctx, &auction) - if err != nil { - return 0, err - } - return auctionID, nil -} - -// set an auction in the store, adding a new ID, and setting indexes -func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { - // get ID - newAuctionID, err := k.getNextAuctionID(ctx) - if err != nil { - return 0, err - } - // set ID - auction.SetID(newAuctionID) - - // store auction - k.SetAuction(ctx, auction) - k.incrementNextAuctionID(ctx) - return newAuctionID, nil -} - -// PlaceBid places a bid on any auction. -func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { - - // get auction from store - auction, found := k.GetAuction(ctx, auctionID) - if !found { - return sdk.ErrInternal("auction doesn't exist") - } - - // check end time - if ctx.BlockTime().After(auction.GetEndTime()) { - return sdk.ErrInternal("auction has closed") - } - - var err sdk.Error - var a types.Auction - switch auc := auction.(type) { - case types.ForwardAuction: - a, err = k.PlaceBidForward(ctx, auc, bidder, bid) - if err != nil { - return err - } - case types.ReverseAuction: - a, err = k.PlaceBidReverse(ctx, auc, bidder, lot) - if err != nil { - return err - } - case types.ForwardReverseAuction: - a, err = k.PlaceBidForwardReverse(ctx, auc, bidder, bid, lot) - if err != nil { - return err - } - default: - panic("unrecognized auction type") - } - - // store updated auction - k.SetAuction(ctx, a) // TODO maybe move into above funcs - - return nil -} - -func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) { - // Valid New Bid - if bid.Denom != a.Bid.Denom { - return a, sdk.ErrInternal("bid denom doesn't match auction") - } - if !a.Bid.IsLT(bid) { // TODO add minimum bid size - return a, sdk.ErrInternal("bid not greater than last bid") - } - - // Move Coins - increment := bid.Sub(a.Bid) - bidAmtToReturn := a.Bid - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) - } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(increment))) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(increment)) // increase in bid size is burned - if err != nil { - return a, err - } - err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(increment)) - if err != nil { - return a, err - } - - // Update Auction - a.Bidder = bidder - a.Bid = bid - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) // TODO write a min func for time types - - return a, nil -} -func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { - // Validate New Bid // TODO min bid increments, make validation code less confusing - if !a.Bid.IsEqual(a.MaxBid) { - // Auction is in forward phase, a bid here can put the auction into forward or reverse phases - if !a.Bid.IsLT(bid) { - return a, sdk.ErrInternal("auction in forward phase, new bid not higher than last bid") - } - if a.MaxBid.IsLT(bid) { - return a, sdk.ErrInternal("bid higher than max bid") - } - if lot.IsNegative() || a.Lot.IsLT(lot) { - return a, sdk.ErrInternal("lot out of bounds") - } - if lot.IsLT(a.Lot) && !bid.IsEqual(a.MaxBid) { - return a, sdk.ErrInternal("auction cannot enter reverse phase without bidding max bid") - } - } else { - // Auction is in reverse phase, it can never leave reverse phase - if !bid.IsEqual(a.MaxBid) { - return a, sdk.ErrInternal("") // not necessary - } - if lot.IsNegative() { - return a, sdk.ErrInternal("can't bid negative amount") - } - if !lot.IsLT(a.Lot) { - return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") - } - } - - // Move Coins - bidIncrement := bid.Sub(a.Bid) - bidAmtToReturn := a.Bid - lotDecrement := a.Lot.Sub(lot) - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) - } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(bidIncrement))) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(bidIncrement)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.OtherPerson, sdk.NewCoins(lotDecrement)) - if err != nil { - return a, err - } - - // Update Auction - a.Bidder = bidder - a.Lot = lot - a.Bid = bid - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) - - return types.ForwardReverseAuction{}, nil -} -func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { - // Validate New Bid - if lot.Denom != a.Lot.Denom { - return a, sdk.ErrInternal("lot denom doesn't match auction") - } - if lot.IsNegative() { - return a, sdk.ErrInternal("lot less than 0") - } - if !lot.IsLT(a.Lot) { // TODO add min bid decrements - return a, sdk.ErrInternal("lot not smaller than last lot") - } - - // Move Coins - bidAmtToReturn := a.Bid - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) - } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - - // Update Auction - a.Bidder = bidder - a.Lot = lot - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) - - return a, nil -} - -// CloseAuction closes an auction and distributes funds to the highest bidder. -func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { - - // get the auction from the store - auction, found := k.GetAuction(ctx, auctionID) - if !found { - return sdk.ErrInternal("auction doesn't exist") - } - // error if auction has not reached the end time - if ctx.BlockTime().Before(auction.GetEndTime()) { - return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block time (%v) is under auction end time (%v)", ctx.BlockTime(), auction.GetEndTime())) - } - - // payout to the last bidder - var err sdk.Error - switch auc := auction.(type) { - case types.ForwardAuction, types.ForwardReverseAuction: - err = k.PayoutAuctionLot(ctx, auc) - if err != nil { - return err - } - case types.ReverseAuction: - err = k.MintAndPayoutAuctionLot(ctx, auc) - if err != nil { - return err - } - default: - panic("unrecognized auction type") - } - - // Delete auction from store (and queue) - k.DeleteAuction(ctx, auctionID) - - return nil -} -func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) sdk.Error { - err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) - if err != nil { - return err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, a.Initiator, a.Bidder, sdk.NewCoins(a.Lot)) - if err != nil { - return err - } - return nil -} -func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBidder(), sdk.NewCoins(a.GetLot())) - if err != nil { - return err - } - return nil -} - -// ---------- Store methods ---------- -// Use these to add and remove auction from the store. - -// getNextAuctionID gets the next available global AuctionID -func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { - // get next ID from store - store := ctx.KVStore(k.storeKey) - bz := store.Get(k.getNextAuctionIDKey()) - if bz == nil { - // if not found, set the id at 0 - bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) - store.Set(k.getNextAuctionIDKey(), bz) - // TODO Set auction ID in genesis - //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") - } - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) - return auctionID, nil -} - -// incrementNextAuctionID increments the global ID in the store by 1 -func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error { - // get next ID from store - store := ctx.KVStore(k.storeKey) - bz := store.Get(k.getNextAuctionIDKey()) - if bz == nil { - panic("initial auctionID never set in genesis") - //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO - } - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) - - // increment the stored next ID - bz = k.cdc.MustMarshalBinaryLengthPrefixed(auctionID + 1) - store.Set(k.getNextAuctionIDKey(), bz) - - return 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 queue if it is already in there - existingAuction, found := k.GetAuction(ctx, auction.GetID()) - if found { - k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) - } - - // store auction - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction) - store.Set(k.getAuctionKey(auction.GetID()), bz) - - // add to the queue - k.InsertIntoQueue(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 := ctx.KVStore(k.storeKey) - bz := store.Get(k.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 queue - auction, found := k.GetAuction(ctx, auctionID) - if found { - k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) - } - - // delete auction - store := ctx.KVStore(k.storeKey) - store.Delete(k.getAuctionKey(auctionID)) -} - -// ---------- Queue and key methods ---------- -// These are lower level function used by the store methods above. - -func (k Keeper) getNextAuctionIDKey() []byte { - return []byte("nextAuctionID") -} -func (k Keeper) getAuctionKey(auctionID types.ID) []byte { - return []byte(fmt.Sprintf("auctions:%d", auctionID)) -} - -// Inserts a AuctionID into the queue at endTime -func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { - // get the store - store := ctx.KVStore(k.storeKey) - // marshal thing to be inserted - bz := k.cdc.MustMarshalBinaryLengthPrefixed(auctionID) - // store it - store.Set( - getQueueElementKey(endTime, auctionID), - bz, - ) -} - -// removes an auctionID from the queue -func (k Keeper) removeFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { - store := ctx.KVStore(k.storeKey) - store.Delete(getQueueElementKey(endTime, auctionID)) -} - -// Returns an iterator for all the auctions in the queue that expire by endTime -func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? - // get store - store := ctx.KVStore(k.storeKey) - // get an interator - return store.Iterator( - queueKeyPrefix, // start key - sdk.PrefixEndBytes(getQueueElementKeyPrefix(endTime)), // end key (apparently exclusive but tests suggested otherwise) - ) -} - -// GetAuctionIterator returns an iterator over all auctions in the store -func (k Keeper) GetAuctionIterator(ctx sdk.Context) sdk.Iterator { - store := ctx.KVStore(k.storeKey) - return sdk.KVStorePrefixIterator(store, nil) -} - -var queueKeyPrefix = []byte("queue") -var keyDelimiter = []byte(":") - -// Returns half a key for an auctionID in the queue, it missed the id off the end -func getQueueElementKeyPrefix(endTime time.Time) []byte { - return bytes.Join([][]byte{ - queueKeyPrefix, - sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering - }, keyDelimiter) -} - -// Returns the key for an auctionID in the queue -func getQueueElementKey(endTime time.Time, auctionID types.ID) []byte { - return bytes.Join([][]byte{ - queueKeyPrefix, - sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering - sdk.Uint64ToBigEndian(uint64(auctionID)), - }, keyDelimiter) -} - -// GetAuctionID returns the id from an input Auction -func (k Keeper) DecodeAuctionID(ctx sdk.Context, idBytes []byte) types.ID { - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(idBytes, &auctionID) - return auctionID -} - -func (k Keeper) DecodeAuction(ctx sdk.Context, auctionBytes []byte) types.Auction { - var auction types.Auction - k.cdc.MustUnmarshalBinaryBare(auctionBytes, &auction) - return auction -} diff --git a/x/auction/keeper/store.go b/x/auction/keeper/store.go new file mode 100644 index 00000000..04ee36a3 --- /dev/null +++ b/x/auction/keeper/store.go @@ -0,0 +1,187 @@ +package keeper + +import ( + "bytes" + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/auction/types" +) + +// set an auction in the store, adding a new ID, and setting indexes +func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { + // get ID + newAuctionID, err := k.getNextAuctionID(ctx) + if err != nil { + return 0, err + } + // set ID + auction.SetID(newAuctionID) + + // store auction + k.SetAuction(ctx, auction) + k.incrementNextAuctionID(ctx) + return newAuctionID, nil +} + +// getNextAuctionID gets the next available global AuctionID +func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { + // get next ID from store + store := ctx.KVStore(k.storeKey) + bz := store.Get(k.getNextAuctionIDKey()) + if bz == nil { + // if not found, set the id at 0 + bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) + store.Set(k.getNextAuctionIDKey(), bz) + // TODO Set auction ID in genesis + //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + } + var auctionID types.ID + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) + return auctionID, nil +} + +// incrementNextAuctionID increments the global ID in the store by 1 +func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error { + // get next ID from store + store := ctx.KVStore(k.storeKey) + bz := store.Get(k.getNextAuctionIDKey()) + if bz == nil { + panic("initial auctionID never set in genesis") + //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO + } + var auctionID types.ID + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) + + // increment the stored next ID + bz = k.cdc.MustMarshalBinaryLengthPrefixed(auctionID + 1) + store.Set(k.getNextAuctionIDKey(), bz) + + return 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 queue if it is already in there + existingAuction, found := k.GetAuction(ctx, auction.GetID()) + if found { + k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) + } + + // store auction + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction) + store.Set(k.getAuctionKey(auction.GetID()), bz) + + // add to the queue + k.InsertIntoQueue(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 := ctx.KVStore(k.storeKey) + bz := store.Get(k.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 queue + auction, found := k.GetAuction(ctx, auctionID) + if found { + k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) + } + + // delete auction + store := ctx.KVStore(k.storeKey) + store.Delete(k.getAuctionKey(auctionID)) +} + +// ---------- Queue and key methods ---------- +// These are lower level function used by the store methods above. + +func (k Keeper) getNextAuctionIDKey() []byte { + return []byte("nextAuctionID") +} +func (k Keeper) getAuctionKey(auctionID types.ID) []byte { + return []byte(fmt.Sprintf("auctions:%d", auctionID)) +} + +// Inserts a AuctionID into the queue at endTime +func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { + // get the store + store := ctx.KVStore(k.storeKey) + // marshal thing to be inserted + bz := k.cdc.MustMarshalBinaryLengthPrefixed(auctionID) + // store it + store.Set( + getQueueElementKey(endTime, auctionID), + bz, + ) +} + +// removes an auctionID from the queue +func (k Keeper) removeFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { + store := ctx.KVStore(k.storeKey) + store.Delete(getQueueElementKey(endTime, auctionID)) +} + +// Returns an iterator for all the auctions in the queue that expire by endTime +func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? + // get store + store := ctx.KVStore(k.storeKey) + // get an interator + return store.Iterator( + queueKeyPrefix, // start key + sdk.PrefixEndBytes(getQueueElementKeyPrefix(endTime)), // end key (apparently exclusive but tests suggested otherwise) + ) +} + +// GetAuctionIterator returns an iterator over all auctions in the store +func (k Keeper) GetAuctionIterator(ctx sdk.Context) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return sdk.KVStorePrefixIterator(store, nil) +} + +var queueKeyPrefix = []byte("queue") +var keyDelimiter = []byte(":") + +// Returns half a key for an auctionID in the queue, it missed the id off the end +func getQueueElementKeyPrefix(endTime time.Time) []byte { + return bytes.Join([][]byte{ + queueKeyPrefix, + sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering + }, keyDelimiter) +} + +// Returns the key for an auctionID in the queue +func getQueueElementKey(endTime time.Time, auctionID types.ID) []byte { + return bytes.Join([][]byte{ + queueKeyPrefix, + sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering + sdk.Uint64ToBigEndian(uint64(auctionID)), + }, keyDelimiter) +} + +// GetAuctionID returns the id from an input Auction +func (k Keeper) DecodeAuctionID(ctx sdk.Context, idBytes []byte) types.ID { + var auctionID types.ID + k.cdc.MustUnmarshalBinaryLengthPrefixed(idBytes, &auctionID) + return auctionID +} + +func (k Keeper) DecodeAuction(ctx sdk.Context, auctionBytes []byte) types.Auction { + var auction types.Auction + k.cdc.MustUnmarshalBinaryBare(auctionBytes, &auction) + return auction +} From 5363541de38fdeec230920f467d455bcb58d5749 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 21 Dec 2019 01:04:04 +0000 Subject: [PATCH 04/27] update store methods --- app/app.go | 18 +-- x/auction/abci.go | 19 ++- x/auction/alias.go | 4 - x/auction/genesis.go | 11 +- x/auction/keeper/auctions.go | 6 + x/auction/keeper/keeper_test.go | 279 +++++++++++++++++++------------- x/auction/keeper/querier.go | 17 +- x/auction/keeper/store.go | 205 ++++++++++------------- x/auction/types/auctions.go | 42 +++-- x/auction/types/keys.go | 22 +++ 10 files changed, 332 insertions(+), 291 deletions(-) diff --git a/app/app.go b/app/app.go index 088e26a5..90f4f5fe 100644 --- a/app/app.go +++ b/app/app.go @@ -151,7 +151,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace) cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace) - liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace) + //liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace) pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace) // add keepers @@ -237,16 +237,16 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.bankKeeper) app.auctionKeeper = auction.NewKeeper( app.cdc, - app.cdpKeeper, // CDP keeper standing in for bank keys[auction.StoreKey], + app.supplyKeeper, // CDP keeper standing in for bank auctionSubspace) - app.liquidatorKeeper = liquidator.NewKeeper( - app.cdc, - keys[liquidator.StoreKey], - liquidatorSubspace, - app.cdpKeeper, - app.auctionKeeper, - app.cdpKeeper) // CDP keeper standing in for bank + // app.liquidatorKeeper = liquidator.NewKeeper( + // app.cdc, + // keys[liquidator.StoreKey], + // liquidatorSubspace, + // app.cdpKeeper, + // app.auctionKeeper, + // app.cdpKeeper) // CDP keeper standing in for bank // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks diff --git a/x/auction/abci.go b/x/auction/abci.go index 164b35b7..f05a518a 100644 --- a/x/auction/abci.go +++ b/x/auction/abci.go @@ -7,17 +7,16 @@ import ( // EndBlocker runs at the end of every block. func EndBlocker(ctx sdk.Context, k Keeper) { - // get an iterator of expired auctions - expiredAuctions := k.GetQueueIterator(ctx, EndTime(ctx.BlockHeight())) - defer expiredAuctions.Close() - - // loop through and close them - distribute funds, delete from store (and queue) - for ; expiredAuctions.Valid(); expiredAuctions.Next() { - - auctionID := k.DecodeAuctionID(ctx, expiredAuctions.Value()) - err := k.CloseAuction(ctx, auctionID) + var expiredAuctions []ID + k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id ID) bool { + expiredAuctions = append(expiredAuctions, id) + return false + }) + // Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration + for _, id := range expiredAuctions { + err := k.CloseAuction(ctx, id) if err != nil { - panic(err) // TODO how should errors be handled here? + panic(err) } } } diff --git a/x/auction/alias.go b/x/auction/alias.go index 9f86f9e7..33b64525 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -49,13 +49,9 @@ type ( Auction = types.Auction BaseAuction = types.BaseAuction ID = types.ID - EndTime = types.EndTime - BankInput = types.BankInput - BankOutput = types.BankOutput ForwardAuction = types.ForwardAuction ReverseAuction = types.ReverseAuction ForwardReverseAuction = types.ForwardReverseAuction - BankKeeper = types.BankKeeper GenesisAuctions = types.GenesisAuctions GenesisState = types.GenesisState MsgPlaceBid = types.MsgPlaceBid diff --git a/x/auction/genesis.go b/x/auction/genesis.go index 1b7a327c..7b16562b 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -18,13 +18,10 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { params := keeper.GetParams(ctx) var genAuctions GenesisAuctions - iterator := keeper.GetAuctionIterator(ctx) + keeper.IterateAuctions(ctx, func(a Auction) bool { + genAuctions = append(genAuctions, a) + return false + }) - for ; iterator.Valid(); iterator.Next() { - - auction := keeper.DecodeAuction(ctx, iterator.Value()) - genAuctions = append(genAuctions, auction) - - } return NewGenesisState(params, genAuctions) } diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 9a5630d6..b5ca5850 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/supply" @@ -295,3 +296,8 @@ func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { } return nil } + +// FIXME stand in func for compiler +func earliestTime(t1, t2 time.Time) time.Time { + return t1 +} diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 873dcb81..56d6065f 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -12,118 +13,118 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -func TestKeeper_ForwardAuction(t *testing.T) { - // Setup - _, addrs := app.GeneratePrivKeyAddressPairs(2) - seller := addrs[0] - buyer := addrs[1] +// func TestKeeper_ForwardAuction(t *testing.T) { +// // Setup +// _, addrs := app.GeneratePrivKeyAddressPairs(2) +// seller := addrs[0] +// buyer := addrs[1] - tApp := app.NewTestApp() - tApp.InitializeFromGenesisStates( - app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), - ) +// tApp := app.NewTestApp() +// tApp.InitializeFromGenesisStates( +// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), +// ) - ctx := tApp.NewContext(false, abci.Header{}) - keeper := tApp.GetAuctionKeeper() +// ctx := tApp.NewContext(false, abci.Header{}) +// keeper := tApp.GetAuctionKeeper() - // Create an auction (lot: 20 t1, initialBid: 0 t2) - auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid - require.NoError(t, err) - // Check seller's coins have decreased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) +// // Create an auction (lot: 20 t1, initialBid: 0 t2) +// auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid +// require.NoError(t, err) +// // Check seller's coins have decreased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) - // PlaceBid (bid: 10 t2, lot: same as starting) - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot - // Check buyer's coins have decreased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) - // Check seller's coins have increased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) +// // PlaceBid (bid: 10 t2, lot: same as starting) +// require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot +// // Check buyer's coins have decreased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) +// // Check seller's coins have increased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) - // Close auction at just after auction expiry - ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) - require.NoError(t, keeper.CloseAuction(ctx, auctionID)) - // Check buyer's coins increased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) -} +// // Close auction at just after auction expiry +// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) +// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) +// // Check buyer's coins increased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) +// } -func TestKeeper_ReverseAuction(t *testing.T) { - // Setup - _, addrs := app.GeneratePrivKeyAddressPairs(2) - seller := addrs[0] - buyer := addrs[1] +// func TestKeeper_ReverseAuction(t *testing.T) { +// // Setup +// _, addrs := app.GeneratePrivKeyAddressPairs(2) +// seller := addrs[0] +// buyer := addrs[1] - tApp := app.NewTestApp() - tApp.InitializeFromGenesisStates( - app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), - ) +// tApp := app.NewTestApp() +// tApp.InitializeFromGenesisStates( +// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), +// ) - ctx := tApp.NewContext(false, abci.Header{}) - keeper := tApp.GetAuctionKeeper() +// ctx := tApp.NewContext(false, abci.Header{}) +// keeper := tApp.GetAuctionKeeper() - // Start auction - auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot - require.NoError(t, err) - // Check buyer's coins have decreased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1))) +// // Start auction +// auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot +// require.NoError(t, err) +// // Check buyer's coins have decreased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1))) - // Place a bid - require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot - // Check seller's coins have decreased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) - // Check buyer's coins have increased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) +// // Place a bid +// require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot +// // Check seller's coins have decreased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) +// // Check buyer's coins have increased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) - // Close auction at just after auction expiry - ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) - require.NoError(t, keeper.CloseAuction(ctx, auctionID)) - // Check seller's coins increased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) -} +// // Close auction at just after auction expiry +// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) +// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) +// // Check seller's coins increased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) +// } -func TestKeeper_ForwardReverseAuction(t *testing.T) { - // Setup - _, addrs := app.GeneratePrivKeyAddressPairs(3) - seller := addrs[0] - buyer := addrs[1] - recipient := addrs[2] +// func TestKeeper_ForwardReverseAuction(t *testing.T) { +// // Setup +// _, addrs := app.GeneratePrivKeyAddressPairs(3) +// seller := addrs[0] +// buyer := addrs[1] +// recipient := addrs[2] - tApp := app.NewTestApp() - tApp.InitializeFromGenesisStates( - app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), - ) +// tApp := app.NewTestApp() +// tApp.InitializeFromGenesisStates( +// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), +// ) - ctx := tApp.NewContext(false, abci.Header{}) - keeper := tApp.GetAuctionKeeper() +// ctx := tApp.NewContext(false, abci.Header{}) +// keeper := tApp.GetAuctionKeeper() - // Start auction - auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson - require.NoError(t, err) - // Check seller's coins have decreased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) +// // Start auction +// auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson +// require.NoError(t, err) +// // Check seller's coins have decreased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) - // Place a bid - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot - // Check bidder's coins have decreased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) - // Check seller's coins have increased - tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150))) - // Check "recipient" has received coins - tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) +// // Place a bid +// require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot +// // Check bidder's coins have decreased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) +// // Check seller's coins have increased +// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150))) +// // Check "recipient" has received coins +// tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) - // Close auction at just after auction expiry - ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) - require.NoError(t, keeper.CloseAuction(ctx, auctionID)) - // Check buyer's coins increased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) -} +// // Close auction at just after auction expiry +// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) +// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) +// // Check buyer's coins increased +// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) +// } -func TestKeeper_SetGetDeleteAuction(t *testing.T) { +func SetGetDeleteAuction(t *testing.T) { // setup keeper, create auction - _, addrs := app.GeneratePrivKeyAddressPairs(1) tApp := app.NewTestApp() keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) - auction, _ := types.NewForwardAuction(addrs[0], c("usdx", 100), c("kava", 0), types.EndTime(1000)) + someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal + auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime) id := types.ID(5) auction.SetID(id) @@ -135,9 +136,9 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) { require.True(t, found) require.Equal(t, &auction, readAuction) // check auction is in queue - iter := keeper.GetQueueIterator(ctx, 100000) - require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) - iter.Close() + // iter := keeper.GetQueueIterator(ctx, 100000) + // require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) + // iter.Close() // delete auction keeper.DeleteAuction(ctx, id) @@ -146,41 +147,93 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) { _, found = keeper.GetAuction(ctx, id) require.False(t, found) // check auction not in queue - iter = keeper.GetQueueIterator(ctx, 100000) - require.Equal(t, 0, len(convertIteratorToSlice(keeper, iter))) - iter.Close() + // iter = keeper.GetQueueIterator(ctx, 100000) + // require.Equal(t, 0, len(convertIteratorToSlice(keeper, iter))) + // iter.Close() } -// TODO convert to table driven test with more test cases -func TestKeeper_ExpiredAuctionQueue(t *testing.T) { +func TestIncrementNextAuctionID(t *testing.T) { // setup keeper tApp := app.NewTestApp() keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) - // create an example queue - type queue []struct { - endTime types.EndTime + // store id + id := types.ID(123456) + keeper.SetNextAuctionID(ctx, id) + + require.NoError(t, keeper.IncrementNextAuctionID(ctx)) + + // check id was incremented + readID, err := keeper.GetNextAuctionID(ctx) + require.NoError(t, err) + require.Equal(t, id+1, readID) + +} + +// func TestIterateAuctions(t *testing.T) { +// // setup keeper +// tApp := app.NewTestApp() +// keeper := tApp.GetAuctionKeeper() +// ctx := tApp.NewContext(true, abci.Header{}) + +// auctions := []types.Auction{ +// &types.ForwardAuction{}, +// } +// for _, a := range auctions { +// keeper.SetAuction(ctx, a) +// } + +// var readAuctions []types.Auction +// keeper.IterateAuctions(ctx, func(a types.Auction) bool { +// readAuctions = append(readAuctions, a) +// return false +// }) + +// require.Equal(t, auctions, readAuctions) +// } + +func TestIterateAuctionsByTime(t *testing.T) { + // setup keeper + tApp := app.NewTestApp() + keeper := tApp.GetAuctionKeeper() + ctx := tApp.NewContext(true, abci.Header{}) + + // create a list of times + queue := []struct { + endTime time.Time auctionID types.ID + }{ + {time.Date(84, time.January, 1, 0, 0, 0, 0, time.UTC), 34345345}, + {time.Date(98, time.January, 2, 0, 0, 0, 0, time.UTC), 5}, + {time.Date(98, time.January, 2, 13, 5, 0, 0, time.UTC), 6}, + {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 1}, + {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 3}, + {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 4}, + {time.Date(98, time.January, 2, 16, 0, 0, 1, time.UTC), 0}, // TODO tidy up redundant entries + } + cutoffTime := time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC) + + var expectedQueue []types.ID + for _, i := range queue { + if i.endTime.After(cutoffTime) { // only append items where endTime ≤ cutoffTime + break + } + expectedQueue = append(expectedQueue, i.auctionID) } - q := queue{{1000, 0}, {1300, 2}, {5200, 1}} // write and read queue - for _, v := range q { + for _, v := range queue { keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID) } - iter := keeper.GetQueueIterator(ctx, 1000) - - // check before and after match - i := 0 - for ; iter.Valid(); iter.Next() { - var auctionID types.ID - tApp.Codec().MustUnmarshalBinaryLengthPrefixed(iter.Value(), &auctionID) - require.Equal(t, q[i].auctionID, auctionID) - i++ - } + var readQueue []types.ID + keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id types.ID) bool { + readQueue = append(readQueue, id) + return false + }) + require.Equal(t, expectedQueue, readQueue) } func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID { diff --git a/x/auction/keeper/querier.go b/x/auction/keeper/querier.go index 15748733..3b92476e 100644 --- a/x/auction/keeper/querier.go +++ b/x/auction/keeper/querier.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/auction/types" @@ -20,18 +21,14 @@ func NewQuerier(keeper Keeper) sdk.Querier { } func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var AuctionsList types.QueryResAuctions + var auctionsList types.QueryResAuctions - iterator := keeper.GetAuctionIterator(ctx) + keeper.IterateAuctions(ctx, func(a types.Auction) bool { + auctionsList = append(auctionsList, fmt.Sprintf("%+v", a)) // TODO formatting + return false + }) - for ; iterator.Valid(); iterator.Next() { - - var auction types.Auction - keeper.cdc.MustUnmarshalBinaryBare(iterator.Value(), &auction) - AuctionsList = append(AuctionsList, auction.String()) - } - - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, AuctionsList) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, auctionsList) if err2 != nil { panic("could not marshal result to JSON") } diff --git a/x/auction/keeper/store.go b/x/auction/keeper/store.go index 04ee36a3..f65375a6 100644 --- a/x/auction/keeper/store.go +++ b/x/auction/keeper/store.go @@ -1,91 +1,84 @@ package keeper import ( - "bytes" - "fmt" "time" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/auction/types" ) -// set an auction in the store, adding a new ID, and setting indexes -func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { - // get ID - newAuctionID, err := k.getNextAuctionID(ctx) - if err != nil { - return 0, err - } - // set ID - auction.SetID(newAuctionID) - - // store auction - k.SetAuction(ctx, auction) - k.incrementNextAuctionID(ctx) - return newAuctionID, nil +// 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 gets the next available global AuctionID -func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { - // get next ID from store +// GetNextAuctionID reads the next available global ID from store +func (k Keeper) GetNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { store := ctx.KVStore(k.storeKey) - bz := store.Get(k.getNextAuctionIDKey()) + bz := store.Get(types.NextAuctionIDKey) if bz == nil { - // if not found, set the id at 0 - bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) - store.Set(k.getNextAuctionIDKey(), bz) - // TODO Set auction ID in genesis - //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + //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") } - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) - return auctionID, nil + return types.NewIDFromBytes(bz), nil } // incrementNextAuctionID increments the global ID in the store by 1 -func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error { - // get next ID from store - store := ctx.KVStore(k.storeKey) - bz := store.Get(k.getNextAuctionIDKey()) - if bz == nil { - panic("initial auctionID never set in genesis") - //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO +func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { + id, err := k.GetNextAuctionID(ctx) + if err != nil { + return err } - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) - - // increment the stored next ID - bz = k.cdc.MustMarshalBinaryLengthPrefixed(auctionID + 1) - store.Set(k.getNextAuctionIDKey(), bz) - + k.SetNextAuctionID(ctx, id+1) return nil } +// storeNewAuction stores an auction, adding a new ID, and setting indexes +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.SetID(newAuctionID) + + k.SetAuction(ctx, auction) + + err = k.IncrementNextAuctionID(ctx) + if err != nil { + return 0, err + } + return newAuctionID, nil +} + +// TODO should get/set/delete be responsible for updating auctionByTime index? + // 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 queue if it is already in there - existingAuction, found := k.GetAuction(ctx, auction.GetID()) - if found { - k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) - } + // existingAuction, found := k.GetAuction(ctx, auction.GetID()) + // if found { + // k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) + // } // store auction - store := ctx.KVStore(k.storeKey) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction) - store.Set(k.getAuctionKey(auction.GetID()), bz) + store.Set(types.GetAuctionKey(auction.GetID()), bz) // add to the queue - k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) + //k.InsertIntoQueue(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 := ctx.KVStore(k.storeKey) - bz := store.Get(k.getAuctionKey(auctionID)) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) + bz := store.Get(types.GetAuctionKey(auctionID)) if bz == nil { return auction, false } @@ -97,91 +90,59 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, // DeleteAuction removes an auction from the store without any validation func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { // remove from queue - auction, found := k.GetAuction(ctx, auctionID) - if found { - k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) - } + //auction, found := k.GetAuction(ctx, auctionID) + // if found { + // k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) + // } // delete auction - store := ctx.KVStore(k.storeKey) - store.Delete(k.getAuctionKey(auctionID)) -} - -// ---------- Queue and key methods ---------- -// These are lower level function used by the store methods above. - -func (k Keeper) getNextAuctionIDKey() []byte { - return []byte("nextAuctionID") -} -func (k Keeper) getAuctionKey(auctionID types.ID) []byte { - return []byte(fmt.Sprintf("auctions:%d", auctionID)) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) + store.Delete(types.GetAuctionKey(auctionID)) } // Inserts a AuctionID into the queue at endTime func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { - // get the store - store := ctx.KVStore(k.storeKey) - // marshal thing to be inserted - bz := k.cdc.MustMarshalBinaryLengthPrefixed(auctionID) - // store it - store.Set( - getQueueElementKey(endTime, auctionID), - bz, - ) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) + store.Set(types.GetAuctionByTimeKey(endTime, auctionID), auctionID.Bytes()) } // removes an auctionID from the queue -func (k Keeper) removeFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { - store := ctx.KVStore(k.storeKey) - store.Delete(getQueueElementKey(endTime, auctionID)) +func (k Keeper) RemoveFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) + store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) } -// Returns an iterator for all the auctions in the queue that expire by endTime -func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? - // get store - store := ctx.KVStore(k.storeKey) - // get an interator - return store.Iterator( - queueKeyPrefix, // start key - sdk.PrefixEndBytes(getQueueElementKeyPrefix(endTime)), // end key (apparently exclusive but tests suggested otherwise) +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 + } + } } -// GetAuctionIterator returns an iterator over all auctions in the store -func (k Keeper) GetAuctionIterator(ctx sdk.Context) sdk.Iterator { - store := ctx.KVStore(k.storeKey) - return sdk.KVStorePrefixIterator(store, nil) -} +// IterateAuctions provides an iterator over all stored auctions. For +// each auction, cb will be called. If the 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) -var queueKeyPrefix = []byte("queue") -var keyDelimiter = []byte(":") + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var auction types.Auction + k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auction) -// Returns half a key for an auctionID in the queue, it missed the id off the end -func getQueueElementKeyPrefix(endTime time.Time) []byte { - return bytes.Join([][]byte{ - queueKeyPrefix, - sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering - }, keyDelimiter) -} - -// Returns the key for an auctionID in the queue -func getQueueElementKey(endTime time.Time, auctionID types.ID) []byte { - return bytes.Join([][]byte{ - queueKeyPrefix, - sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering - sdk.Uint64ToBigEndian(uint64(auctionID)), - }, keyDelimiter) -} - -// GetAuctionID returns the id from an input Auction -func (k Keeper) DecodeAuctionID(ctx sdk.Context, idBytes []byte) types.ID { - var auctionID types.ID - k.cdc.MustUnmarshalBinaryLengthPrefixed(idBytes, &auctionID) - return auctionID -} - -func (k Keeper) DecodeAuction(ctx sdk.Context, auctionBytes []byte) types.Auction { - var auction types.Auction - k.cdc.MustUnmarshalBinaryBare(auctionBytes, &auction) - return auction + if cb(auction) { + break + } + } } diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index aa1e6e96..dbe1a66a 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -1,6 +1,7 @@ package types import ( + "encoding/binary" "fmt" "strconv" "time" @@ -9,6 +10,27 @@ import ( "github.com/cosmos/cosmos-sdk/x/supply" ) +// ID type for auction IDs +type ID uint64 + +// NewIDFromString generate new auction ID from a string +func NewIDFromString(s string) (ID, error) { + n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs + if err != nil { + return 0, err + } + return ID(n), nil +} +func NewIDFromBytes(bz []byte) ID { + return ID(binary.BigEndian.Uint64(bz)) + +} +func (id ID) Bytes() []byte { + bz := make([]byte, 8) + binary.BigEndian.PutUint64(bz, uint64(id)) + return bz +} + // Auction is an interface to several types of auction. type Auction interface { GetID() ID @@ -29,23 +51,11 @@ type BaseAuction struct { MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } -// ID type for auction IDs -type ID uint64 - -// NewIDFromString generate new auction ID from a string -func NewIDFromString(s string) (ID, error) { - n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs - if err != nil { - return 0, err - } - return ID(n), nil -} - // GetID getter for auction ID func (a *BaseAuction) GetID() ID { return a.ID } // SetID setter for auction ID -func (a *BaseAuction) SetID(id ID) { a.ID = id } +func (a *BaseAuction) SetID(id ID) { a.ID = id } // TODO if this returns a new auction with ID then no pointers are needed // GetBid getter for auction bid func (a *BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } @@ -76,15 +86,15 @@ type ForwardAuction struct { } // NewForwardAuction creates a new forward auction -func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime time.Time) ForwardAuction { +func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) ForwardAuction { auction := ForwardAuction{&BaseAuction{ // no ID Initiator: seller, Lot: lot, Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided Bid: sdk.NewInt64Coin(bidDenom, 0), - EndTime: EndTime, - MaxEndTime: EndTime, + EndTime: endTime, + MaxEndTime: endTime, }} // output := BankOutput{seller, lot} return auction diff --git a/x/auction/types/keys.go b/x/auction/types/keys.go index cf2153b3..fc20ef14 100644 --- a/x/auction/types/keys.go +++ b/x/auction/types/keys.go @@ -1,5 +1,11 @@ package types +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + const ( // ModuleName The name that will be used throughout the module ModuleName = "auction" @@ -13,3 +19,19 @@ const ( // DefaultParamspace default name for parameter store DefaultParamspace = ModuleName ) + +// TODO use cont to keep immutability? +var ( + AuctionKeyPrefix = []byte{0x00} // prefix for keys that store auctions + AuctionByTimeKeyPrefix = []byte{0x01} // prefix for keys that are part of the auctionsByTime index + + NextAuctionIDKey = []byte{0x02} +) + +func GetAuctionKey(auctionID ID) []byte { + return auctionID.Bytes() +} + +func GetAuctionByTimeKey(endTime time.Time, auctionID ID) []byte { + return append(sdk.FormatTimeBytes(endTime), auctionID.Bytes()...) +} From c786850b1cf7975ffaab8ea41667b9402f8be4ae Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 28 Dec 2019 17:08:51 +0000 Subject: [PATCH 05/27] move store methods to keeper.go --- x/auction/keeper/keeper.go | 142 +++++++++++++++++++++++++++++++++++ x/auction/keeper/store.go | 148 ------------------------------------- 2 files changed, 142 insertions(+), 148 deletions(-) delete mode 100644 x/auction/keeper/store.go diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index fe4cd04a..f91774c9 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -1,7 +1,10 @@ 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" @@ -25,3 +28,142 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl 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, and setting indexes +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 +} + +// TODO should get/set/delete be responsible for updating auctionByTime index? + +// 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 queue if it is already in there + // existingAuction, found := k.GetAuction(ctx, auction.GetID()) + // if found { + // k.removeFromQueue(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 the queue + //k.InsertIntoQueue(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 queue + //auction, found := k.GetAuction(ctx, auctionID) + // if found { + // k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) + // } + + // delete auction + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) + store.Delete(types.GetAuctionKey(auctionID)) +} + +// Inserts a AuctionID into the queue at endTime +func (k Keeper) InsertIntoQueue(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()) +} + +// removes an auctionID from the queue +func (k Keeper) RemoveFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) + store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) +} + +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 the 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 + } + } +} diff --git a/x/auction/keeper/store.go b/x/auction/keeper/store.go deleted file mode 100644 index f65375a6..00000000 --- a/x/auction/keeper/store.go +++ /dev/null @@ -1,148 +0,0 @@ -package keeper - -import ( - "time" - - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/kava-labs/kava/x/auction/types" -) - -// 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 -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, and setting indexes -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.SetID(newAuctionID) - - k.SetAuction(ctx, auction) - - err = k.IncrementNextAuctionID(ctx) - if err != nil { - return 0, err - } - return newAuctionID, nil -} - -// TODO should get/set/delete be responsible for updating auctionByTime index? - -// 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 queue if it is already in there - // existingAuction, found := k.GetAuction(ctx, auction.GetID()) - // if found { - // k.removeFromQueue(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 the queue - //k.InsertIntoQueue(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 queue - //auction, found := k.GetAuction(ctx, auctionID) - // if found { - // k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) - // } - - // delete auction - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) - store.Delete(types.GetAuctionKey(auctionID)) -} - -// Inserts a AuctionID into the queue at endTime -func (k Keeper) InsertIntoQueue(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()) -} - -// removes an auctionID from the queue -func (k Keeper) RemoveFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) - store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) -} - -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 the 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 - } - } -} From c867e8ba9eb032e52fbce644119f13f0571987f8 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 28 Dec 2019 17:16:08 +0000 Subject: [PATCH 06/27] move nextAuctionID from params to genState --- x/auction/alias.go | 2 -- x/auction/genesis.go | 9 ++++++++- x/auction/types/genesis.go | 6 ++++-- x/auction/types/params.go | 16 +++------------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/x/auction/alias.go b/x/auction/alias.go index 33b64525..799cc625 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -17,7 +17,6 @@ const ( DefaultParamspace = types.DefaultParamspace DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration DefaultMaxBidDuration = types.DefaultMaxBidDuration - DefaultStartingAuctionID = types.DefaultStartingAuctionID QueryGetAuction = types.QueryGetAuction ) @@ -42,7 +41,6 @@ var ( ModuleCdc = types.ModuleCdc KeyAuctionBidDuration = types.KeyAuctionBidDuration KeyAuctionDuration = types.KeyAuctionDuration - KeyAuctionStartingID = types.KeyAuctionStartingID ) type ( diff --git a/x/auction/genesis.go b/x/auction/genesis.go index 7b16562b..6136df9e 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -6,6 +6,8 @@ import ( // InitGenesis - initializes the store state from genesis data func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetNextAuctionID(ctx, data.NextAuctionID) + keeper.SetParams(ctx, data.AuctionParams) for _, a := range data.Auctions { @@ -15,6 +17,11 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { // ExportGenesis returns a GenesisState for a given context and keeper. func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + nextAuctionID, err := keeper.GetNextAuctionID(ctx) + if err != nil { + panic(err) + } + params := keeper.GetParams(ctx) var genAuctions GenesisAuctions @@ -23,5 +30,5 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { return false }) - return NewGenesisState(params, genAuctions) + return NewGenesisState(nextAuctionID, params, genAuctions) } diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go index 31c1efb5..15530961 100644 --- a/x/auction/types/genesis.go +++ b/x/auction/types/genesis.go @@ -9,13 +9,15 @@ type GenesisAuctions []Auction // GenesisState - auction state that must be provided at genesis type GenesisState struct { + NextAuctionID ID AuctionParams AuctionParams `json:"auction_params" yaml:"auction_params"` Auctions GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"` } // NewGenesisState returns a new genesis state object for auctions module -func NewGenesisState(ap AuctionParams, ga GenesisAuctions) GenesisState { +func NewGenesisState(nextID ID, ap AuctionParams, ga GenesisAuctions) GenesisState { return GenesisState{ + NextAuctionID: nextID, AuctionParams: ap, Auctions: ga, } @@ -23,7 +25,7 @@ func NewGenesisState(ap AuctionParams, ga GenesisAuctions) GenesisState { // DefaultGenesisState defines default genesis state for auction module func DefaultGenesisState() GenesisState { - return NewGenesisState(DefaultAuctionParams(), GenesisAuctions{}) + return NewGenesisState(ID(0), DefaultAuctionParams(), GenesisAuctions{}) } // Equal checks whether two GenesisState structs are equivalent diff --git a/x/auction/types/params.go b/x/auction/types/params.go index af189ed2..8f9b59ac 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -14,8 +14,6 @@ const ( DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour // DefaultBidDuration how long an auction gets extended when someone bids, roughly 3 hours in blocks DefaultMaxBidDuration time.Duration = 3 * time.Hour - // DefaultStartingAuctionID what the id of the first auction will be - DefaultStartingAuctionID ID = ID(0) ) // Parameter keys @@ -23,7 +21,6 @@ var ( // ParamStoreKeyAuctionParams Param store key for auction params KeyAuctionBidDuration = []byte("MaxBidDuration") KeyAuctionDuration = []byte("MaxAuctionDuration") - KeyAuctionStartingID = []byte("StartingAuctionID") ) var _ subspace.ParamSet = &AuctionParams{} @@ -32,15 +29,13 @@ var _ subspace.ParamSet = &AuctionParams{} type AuctionParams struct { MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction, in blocks MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` - StartingAuctionID ID `json:"starting_auction_id" yaml:"starting_auction_id"` } // NewAuctionParams creates a new AuctionParams object -func NewAuctionParams(maxAuctionDuration time.Duration, bidDuration time.Duration, startingID ID) AuctionParams { +func NewAuctionParams(maxAuctionDuration time.Duration, bidDuration time.Duration) AuctionParams { return AuctionParams{ MaxAuctionDuration: maxAuctionDuration, MaxBidDuration: bidDuration, - StartingAuctionID: startingID, } } @@ -49,7 +44,6 @@ func DefaultAuctionParams() AuctionParams { return NewAuctionParams( DefaultMaxAuctionDuration, DefaultMaxBidDuration, - DefaultStartingAuctionID, ) } @@ -65,7 +59,6 @@ func (ap *AuctionParams) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ {KeyAuctionBidDuration, &ap.MaxBidDuration}, {KeyAuctionDuration, &ap.MaxAuctionDuration}, - {KeyAuctionStartingID, &ap.StartingAuctionID}, } } @@ -80,14 +73,11 @@ func (ap AuctionParams) Equal(ap2 AuctionParams) bool { func (ap AuctionParams) String() string { return fmt.Sprintf(`Auction Params: Max Auction Duration: %s - Max Bid Duration: %s - Starting Auction ID: %v`, ap.MaxAuctionDuration, ap.MaxBidDuration, ap.StartingAuctionID) + Max Bid Duration: %s`, ap.MaxAuctionDuration, ap.MaxBidDuration) } // Validate checks that the parameters have valid values. func (ap AuctionParams) Validate() error { - if ap.StartingAuctionID <= ID(0) { - return fmt.Errorf("starting auction ID should be positive, is %v", ap.StartingAuctionID) - } + // TODO check durations are within acceptable limits, if needed return nil } From ac27571d15a9ac7394f279f82ce07287f4762e64 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 28 Dec 2019 17:17:46 +0000 Subject: [PATCH 07/27] simplify auction type to not use pointers --- x/auction/keeper/auctions.go | 2 +- x/auction/types/auctions.go | 39 +++++++++++++++++++++--------------- x/auction/types/codec.go | 6 +++--- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index b5ca5850..f292a083 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -96,7 +96,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr return err } default: - panic("unrecognized auction type") + panic(fmt.Sprintf("unrecognized auction type: %T", auction)) } // store updated auction diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index dbe1a66a..72bd8ed8 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -13,6 +13,7 @@ import ( // ID type for auction IDs type ID uint64 +// TODO can this be removed? // NewIDFromString generate new auction ID from a string func NewIDFromString(s string) (ID, error) { n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs @@ -34,7 +35,7 @@ func (id ID) Bytes() []byte { // Auction is an interface to several types of auction. type Auction interface { GetID() ID - SetID(ID) + WithID(ID) Auction GetBidder() sdk.AccAddress GetLot() sdk.Coin GetEndTime() time.Time @@ -52,21 +53,18 @@ type BaseAuction struct { } // GetID getter for auction ID -func (a *BaseAuction) GetID() ID { return a.ID } - -// SetID setter for auction ID -func (a *BaseAuction) SetID(id ID) { a.ID = id } // TODO if this returns a new auction with ID then no pointers are needed +func (a BaseAuction) GetID() ID { return a.ID } // GetBid getter for auction bid -func (a *BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } +func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } // GetLot getter for auction lot -func (a *BaseAuction) GetLot() sdk.Coin { return a.Lot } +func (a BaseAuction) GetLot() sdk.Coin { return a.Lot } // GetEndTime getter for auction end time -func (a *BaseAuction) GetEndTime() time.Time { return a.EndTime } +func (a BaseAuction) GetEndTime() time.Time { return a.EndTime } -func (a *BaseAuction) String() string { +func (a BaseAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s Lot: %s @@ -82,12 +80,15 @@ func (a *BaseAuction) String() string { // ForwardAuction type for forward auctions type ForwardAuction struct { - *BaseAuction + BaseAuction } +// WithID returns an auction wtih the ID set +func (a ForwardAuction) WithID(id ID) Auction { a.ID = id; return a } + // NewForwardAuction creates a new forward auction func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) ForwardAuction { - auction := ForwardAuction{&BaseAuction{ + auction := ForwardAuction{BaseAuction{ // no ID Initiator: seller, Lot: lot, @@ -102,16 +103,19 @@ func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim // ReverseAuction type for reverse auctions type ReverseAuction struct { - *BaseAuction + BaseAuction } +// WithID returns an auction wtih the ID set +func (a ReverseAuction) WithID(id ID) Auction { a.ID = id; return a } + // NewReverseAuction creates a new reverse auction func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) ReverseAuction { // TODO setting the bidder here is a bit hacky // Needs to be set so that when the first bid is placed, it is paid out to the initiator. // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToModule. Not a problem currently but if checks/logic regarding modules accounts where added to those methods they would be bypassed. // Alternative: set address to nil, and catch it in an if statement in place bid - auction := ReverseAuction{&BaseAuction{ + auction := ReverseAuction{BaseAuction{ // no ID Initiator: buyerModAccName, Lot: initialLot, @@ -125,12 +129,15 @@ func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin // ForwardReverseAuction type for forward reverse auction type ForwardReverseAuction struct { - *BaseAuction + BaseAuction MaxBid sdk.Coin OtherPerson sdk.AccAddress // TODO rename, this is normally the original CDP owner, will have to be updated to account for deposits } -func (a *ForwardReverseAuction) String() string { +// WithID returns an auction wtih the ID set +func (a ForwardReverseAuction) WithID(id ID) Auction { a.ID = id; return a } + +func (a ForwardReverseAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s Lot: %s @@ -149,7 +156,7 @@ func (a *ForwardReverseAuction) String() string { // NewForwardReverseAuction creates a new forward reverse auction func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, otherPerson sdk.AccAddress) ForwardReverseAuction { auction := ForwardReverseAuction{ - BaseAuction: &BaseAuction{ + BaseAuction: BaseAuction{ // no ID Initiator: seller, Lot: lot, diff --git a/x/auction/types/codec.go b/x/auction/types/codec.go index 00e97c8b..75ede175 100644 --- a/x/auction/types/codec.go +++ b/x/auction/types/codec.go @@ -17,7 +17,7 @@ func RegisterCodec(cdc *codec.Codec) { // Register the Auction interface and concrete types cdc.RegisterInterface((*Auction)(nil), nil) - cdc.RegisterConcrete(&ForwardAuction{}, "auction/ForwardAuction", nil) - cdc.RegisterConcrete(&ReverseAuction{}, "auction/ReverseAuction", nil) - cdc.RegisterConcrete(&ForwardReverseAuction{}, "auction/ForwardReverseAuction", nil) + cdc.RegisterConcrete(ForwardAuction{}, "auction/ForwardAuction", nil) + cdc.RegisterConcrete(ReverseAuction{}, "auction/ReverseAuction", nil) + cdc.RegisterConcrete(ForwardReverseAuction{}, "auction/ForwardReverseAuction", nil) } From 0d72f47bc29ffceaff8da2940e604c22fbf5ed0f Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 28 Dec 2019 18:46:53 +0000 Subject: [PATCH 08/27] add basic auction tests --- app/app.go | 10 +- x/auction/keeper/auctions.go | 34 +- x/auction/keeper/auctions_test.go | 145 +++++ x/auction/keeper/integration_test.go | 17 + x/auction/keeper/keeper_test.go | 115 +--- x/auction/types/auctions.go | 14 +- x/auction/types/auctions_test.go | 777 +++++++++++++-------------- 7 files changed, 587 insertions(+), 525 deletions(-) create mode 100644 x/auction/keeper/auctions_test.go create mode 100644 x/auction/keeper/integration_test.go diff --git a/app/app.go b/app/app.go index 90f4f5fe..d8eeec15 100644 --- a/app/app.go +++ b/app/app.go @@ -61,7 +61,7 @@ var ( supply.AppModuleBasic{}, auction.AppModuleBasic{}, cdp.AppModuleBasic{}, - liquidator.AppModuleBasic{}, + //liquidator.AppModuleBasic{}, pricefeed.AppModuleBasic{}, ) @@ -74,6 +74,8 @@ var ( staking.NotBondedPoolName: {supply.Burner, supply.Staking}, gov.ModuleName: {supply.Burner}, validatorvesting.ModuleName: {supply.Burner}, + auction.ModuleName: nil, + liquidator.ModuleName: {supply.Minter, supply.Burner}, } ) @@ -238,7 +240,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.auctionKeeper = auction.NewKeeper( app.cdc, keys[auction.StoreKey], - app.supplyKeeper, // CDP keeper standing in for bank + app.supplyKeeper, auctionSubspace) // app.liquidatorKeeper = liquidator.NewKeeper( // app.cdc, @@ -269,7 +271,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper), auction.NewAppModule(app.auctionKeeper), cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper), - liquidator.NewAppModule(app.liquidatorKeeper), + //liquidator.NewAppModule(app.liquidatorKeeper), pricefeed.NewAppModule(app.pricefeedKeeper), ) @@ -289,7 +291,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, - pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, liquidator.ModuleName, // TODO is this order ok? + pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, //liquidator.ModuleName, // TODO is this order ok? ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index f292a083..2b23da74 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -20,7 +20,7 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin return 0, err } // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) // TODO does this need to be a pointer to satisfy the interface? + auctionID, err := k.storeNewAuction(ctx, auction) if err != nil { return 0, err } @@ -38,7 +38,7 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, return 0, sdk.ErrInternal("module does not have minting permissions") } // store the auction - auctionID, err := k.storeNewAuction(ctx, &auction) + auctionID, err := k.storeNewAuction(ctx, auction) if err != nil { return 0, err } @@ -51,12 +51,12 @@ func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot s auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, otherPerson) // take coins from module account - err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.Coins{lot}) + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { return 0, err } // store the auction - auctionID, err := k.storeNewAuction(ctx, &auction) + auctionID, err := k.storeNewAuction(ctx, auction) if err != nil { return 0, err } @@ -64,6 +64,7 @@ func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot s } // PlaceBid places a bid on any auction. +// TODO passing bid and lot is weird when only one needed func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { // get auction from store @@ -72,11 +73,18 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr return sdk.ErrInternal("auction doesn't exist") } - // check end time + // validate if ctx.BlockTime().After(auction.GetEndTime()) { return sdk.ErrInternal("auction has closed") } + if auction.GetBid().Denom != bid.Denom { + return sdk.ErrInternal("bid has incorrect denom") + } + if auction.GetLot().Denom != lot.Denom { + return sdk.ErrInternal("lot has incorrect denom") + } + // place bid var err sdk.Error var a types.Auction switch auc := auction.(type) { @@ -124,7 +132,7 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder if err != nil { return a, err } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) if err != nil { return a, err } @@ -185,7 +193,7 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu if err != nil { return a, err } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) if err != nil { return a, err } @@ -205,7 +213,7 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu // increment timeout a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) - return types.ForwardReverseAuction{}, nil + return a, nil } func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { // Validate New Bid @@ -228,7 +236,7 @@ func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder if err != nil { return a, err } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, bidder, sdk.NewCoins(bidAmtToReturn)) + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) if err != nil { return a, err } @@ -297,7 +305,11 @@ func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { return nil } -// FIXME stand in func for compiler +// earliestTime returns the earliest of two times. func earliestTime(t1, t2 time.Time) time.Time { - return t1 + if t1.Before(t2) { + return t1 + } else { + return t2 // also returned if times are equal + } } diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go new file mode 100644 index 00000000..1cfd254a --- /dev/null +++ b/x/auction/keeper/auctions_test.go @@ -0,0 +1,145 @@ +package keeper_test + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/supply" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/auction/types" + "github.com/kava-labs/kava/x/liquidator" +) + +func TestForwardAuctionBasic(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(1) + buyer := addrs[0] + sellerModName := liquidator.ModuleName + sellerAddr := supply.NewModuleAddress(sellerModName) + + tApp := app.NewTestApp() + + sellerAcc := supply.NewEmptyModuleAccount(sellerModName, supply.Burner) // forward auctions burn proceeds + require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) + tApp.InitializeFromGenesisStates( + NewAuthGenStateFromAccs(authexported.GenesisAccounts{ + auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + sellerAcc, + }), + ) + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Create an auction (lot: 20 token1, initialBid: 0 token2) + auctionID, err := keeper.StartForwardAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom + require.NoError(t, err) + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) + + // PlaceBid (bid: 10 token, lot: same as starting) + require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10), c("token1", 20))) // bid, lot + // Check buyer's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) + // Check seller's coins have not increased (because proceeds are burned) + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) + + // Close auction at just at auction expiry time + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check buyer's coins increased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) +} + +func TestReverseAuctionBasic(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(1) + seller := addrs[0] + buyerModName := liquidator.ModuleName + buyerAddr := supply.NewModuleAddress(buyerModName) + + tApp := app.NewTestApp() + + tApp.InitializeFromGenesisStates( + NewAuthGenStateFromAccs(authexported.GenesisAccounts{ + auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + supply.NewEmptyModuleAccount(buyerModName, supply.Minter), // reverse auctions mint payout + }), + ) + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Start auction + auctionID, err := keeper.StartReverseAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999)) // buyer, bid, initialLot + require.NoError(t, err) + // Check buyer's coins have not decreased, as lot is minted at the end + tApp.CheckBalance(t, ctx, buyerAddr, nil) // zero coins + + // Place a bid + require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) + // Check buyer's coins have increased + tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20))) + + // Close auction at just after auction expiry + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check seller's coins increased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) +} + +func TestForwardReverseAuctionBasic(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(2) + buyer := addrs[0] + recipient := addrs[1] + sellerModName := liquidator.ModuleName + sellerAddr := supply.NewModuleAddress(sellerModName) + + tApp := app.NewTestApp() + sellerAcc := supply.NewEmptyModuleAccount(sellerModName) + require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) + tApp.InitializeFromGenesisStates( + NewAuthGenStateFromAccs(authexported.GenesisAccounts{ + auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + auth.NewBaseAccount(recipient, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + sellerAcc, + }), + ) + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Start auction + auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson + require.NoError(t, err) + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) + + // Place a forward bid + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot + // Check bidder's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) + // Check seller's coins have increased + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110))) + // Check recipient has not received coins + tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 100), c("token2", 100))) + + // Place a reverse bid + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot + // Check bidder's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) + // Check seller's coins have increased + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150))) + // Check "recipient" has received coins + tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) + + // Close auction at just after auction expiry + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check buyer's coins increased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) +} diff --git a/x/auction/keeper/integration_test.go b/x/auction/keeper/integration_test.go new file mode 100644 index 00000000..3ad2500e --- /dev/null +++ b/x/auction/keeper/integration_test.go @@ -0,0 +1,17 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + + "github.com/kava-labs/kava/app" +) + +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func NewAuthGenStateFromAccs(accounts authexported.GenesisAccounts) app.GenesisState { + authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts) + return app.GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)} +} diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 56d6065f..2e0918af 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -13,128 +13,22 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -// func TestKeeper_ForwardAuction(t *testing.T) { -// // Setup -// _, addrs := app.GeneratePrivKeyAddressPairs(2) -// seller := addrs[0] -// buyer := addrs[1] - -// tApp := app.NewTestApp() -// tApp.InitializeFromGenesisStates( -// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), -// ) - -// ctx := tApp.NewContext(false, abci.Header{}) -// keeper := tApp.GetAuctionKeeper() - -// // Create an auction (lot: 20 t1, initialBid: 0 t2) -// auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid -// require.NoError(t, err) -// // Check seller's coins have decreased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) - -// // PlaceBid (bid: 10 t2, lot: same as starting) -// require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot -// // Check buyer's coins have decreased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) -// // Check seller's coins have increased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) - -// // Close auction at just after auction expiry -// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) -// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) -// // Check buyer's coins increased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) -// } - -// func TestKeeper_ReverseAuction(t *testing.T) { -// // Setup -// _, addrs := app.GeneratePrivKeyAddressPairs(2) -// seller := addrs[0] -// buyer := addrs[1] - -// tApp := app.NewTestApp() -// tApp.InitializeFromGenesisStates( -// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), -// ) - -// ctx := tApp.NewContext(false, abci.Header{}) -// keeper := tApp.GetAuctionKeeper() - -// // Start auction -// auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot -// require.NoError(t, err) -// // Check buyer's coins have decreased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1))) - -// // Place a bid -// require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot -// // Check seller's coins have decreased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) -// // Check buyer's coins have increased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) - -// // Close auction at just after auction expiry -// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) -// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) -// // Check seller's coins increased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) -// } - -// func TestKeeper_ForwardReverseAuction(t *testing.T) { -// // Setup -// _, addrs := app.GeneratePrivKeyAddressPairs(3) -// seller := addrs[0] -// buyer := addrs[1] -// recipient := addrs[2] - -// tApp := app.NewTestApp() -// tApp.InitializeFromGenesisStates( -// app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), -// ) - -// ctx := tApp.NewContext(false, abci.Header{}) -// keeper := tApp.GetAuctionKeeper() - -// // Start auction -// auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson -// require.NoError(t, err) -// // Check seller's coins have decreased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) - -// // Place a bid -// require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot -// // Check bidder's coins have decreased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) -// // Check seller's coins have increased -// tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150))) -// // Check "recipient" has received coins -// tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) - -// // Close auction at just after auction expiry -// ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) -// require.NoError(t, keeper.CloseAuction(ctx, auctionID)) -// // Check buyer's coins increased -// tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) -// } - func SetGetDeleteAuction(t *testing.T) { // setup keeper, create auction tApp := app.NewTestApp() keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal - auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime) id := types.ID(5) - auction.SetID(id) + auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime).WithID(id) // write and read from store - keeper.SetAuction(ctx, &auction) + keeper.SetAuction(ctx, auction) readAuction, found := keeper.GetAuction(ctx, id) // check before and after match require.True(t, found) - require.Equal(t, &auction, readAuction) + require.Equal(t, auction, readAuction) // check auction is in queue // iter := keeper.GetQueueIterator(ctx, 100000) // require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) @@ -245,6 +139,3 @@ func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types } return queue } - -func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } -func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 72bd8ed8..9e45ee5c 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -37,6 +37,7 @@ type Auction interface { GetID() ID WithID(ID) Auction GetBidder() sdk.AccAddress + GetBid() sdk.Coin GetLot() sdk.Coin GetEndTime() time.Time } @@ -44,7 +45,7 @@ type Auction interface { // BaseAuction type shared by all Auctions type BaseAuction struct { ID ID - Initiator string // Module who starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. + Initiator string // Module that starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) @@ -58,6 +59,9 @@ func (a BaseAuction) GetID() ID { return a.ID } // GetBid getter for auction bid func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } +// GetBid getter for auction lot +func (a BaseAuction) GetBid() sdk.Coin { return a.Bid } + // GetLot getter for auction lot func (a BaseAuction) GetLot() sdk.Coin { return a.Lot } @@ -83,7 +87,7 @@ type ForwardAuction struct { BaseAuction } -// WithID returns an auction wtih the ID set +// WithID returns an auction with the ID set func (a ForwardAuction) WithID(id ID) Auction { a.ID = id; return a } // NewForwardAuction creates a new forward auction @@ -97,7 +101,6 @@ func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim EndTime: endTime, MaxEndTime: endTime, }} - // output := BankOutput{seller, lot} return auction } @@ -106,7 +109,7 @@ type ReverseAuction struct { BaseAuction } -// WithID returns an auction wtih the ID set +// WithID returns an auction with the ID set func (a ReverseAuction) WithID(id ID) Auction { a.ID = id; return a } // NewReverseAuction creates a new reverse auction @@ -134,7 +137,7 @@ type ForwardReverseAuction struct { OtherPerson sdk.AccAddress // TODO rename, this is normally the original CDP owner, will have to be updated to account for deposits } -// WithID returns an auction wtih the ID set +// WithID returns an auction with the ID set func (a ForwardReverseAuction) WithID(id ID) Auction { a.ID = id; return a } func (a ForwardReverseAuction) String() string { @@ -167,6 +170,5 @@ func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, ma MaxBid: maxBid, OtherPerson: otherPerson, } - //output := BankOutput{seller, lot} return auction } diff --git a/x/auction/types/auctions_test.go b/x/auction/types/auctions_test.go index b2252f55..bd0db597 100644 --- a/x/auction/types/auctions_test.go +++ b/x/auction/types/auctions_test.go @@ -1,403 +1,396 @@ package types -import ( - "testing" +// // TODO can this be less verbose? Should PlaceBid() be split into smaller functions? +// // It would be possible to combine all auction tests into one test runner. +// func TestForwardAuction_PlaceBid(t *testing.T) { +// seller := sdk.AccAddress([]byte("a_seller")) +// buyer1 := sdk.AccAddress([]byte("buyer1")) +// buyer2 := sdk.AccAddress([]byte("buyer2")) +// end := EndTime(10000) +// now := EndTime(10) - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) +// type args struct { +// currentBlockHeight EndTime +// bidder sdk.AccAddress +// lot sdk.Coin +// bid sdk.Coin +// } +// tests := []struct { +// name string +// auction ForwardAuction +// args args +// expectedOutputs []BankOutput +// expectedInputs []BankInput +// expectedEndTime EndTime +// expectedBidder sdk.AccAddress +// expectedBid sdk.Coin +// expectpass bool +// }{ +// { +// "normal", +// ForwardAuction{BaseAuction{ +// Initiator: seller, +// Lot: c("usdx", 100), +// Bidder: buyer1, +// Bid: c("kava", 6), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, buyer2, c("usdx", 100), c("kava", 10)}, +// []BankOutput{{buyer2, c("kava", 10)}}, +// []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, +// now + DefaultMaxBidDuration, +// buyer2, +// c("kava", 10), +// true, +// }, +// { +// "lowBid", +// ForwardAuction{BaseAuction{ +// Initiator: seller, +// Lot: c("usdx", 100), +// Bidder: buyer1, +// Bid: c("kava", 6), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, buyer2, c("usdx", 100), c("kava", 5)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// buyer1, +// c("kava", 6), +// false, +// }, +// { +// "equalBid", +// ForwardAuction{BaseAuction{ +// Initiator: seller, +// Lot: c("usdx", 100), +// Bidder: buyer1, +// Bid: c("kava", 6), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, buyer2, c("usdx", 100), c("kava", 6)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// buyer1, +// c("kava", 6), +// false, +// }, +// { +// "timeout", +// ForwardAuction{BaseAuction{ +// Initiator: seller, +// Lot: c("usdx", 100), +// Bidder: buyer1, +// Bid: c("kava", 6), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{end + 1, buyer2, c("usdx", 100), c("kava", 10)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// buyer1, +// c("kava", 6), +// false, +// }, +// { +// "hitMaxEndTime", +// ForwardAuction{BaseAuction{ +// Initiator: seller, +// Lot: c("usdx", 100), +// Bidder: buyer1, +// Bid: c("kava", 6), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{end - 1, buyer2, c("usdx", 100), c("kava", 10)}, +// []BankOutput{{buyer2, c("kava", 10)}}, +// []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, +// end, // end time should be capped at MaxEndTime +// buyer2, +// c("kava", 10), +// true, +// }, +// } +// for _, tc := range tests { +// t.Run(tc.name, func(t *testing.T) { +// // update auction and return in/outputs +// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) -// TODO can this be less verbose? Should PlaceBid() be split into smaller functions? -// It would be possible to combine all auction tests into one test runner. -func TestForwardAuction_PlaceBid(t *testing.T) { - seller := sdk.AccAddress([]byte("a_seller")) - buyer1 := sdk.AccAddress([]byte("buyer1")) - buyer2 := sdk.AccAddress([]byte("buyer2")) - end := EndTime(10000) - now := EndTime(10) +// // check for err +// if tc.expectpass { +// require.Nil(t, err) +// } else { +// require.NotNil(t, err) +// } +// // check for correct in/outputs +// require.Equal(t, tc.expectedOutputs, outputs) +// require.Equal(t, tc.expectedInputs, inputs) +// // check for correct EndTime, bidder, bid +// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) +// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) +// require.Equal(t, tc.expectedBid, tc.auction.Bid) +// }) +// } +// } - type args struct { - currentBlockHeight EndTime - bidder sdk.AccAddress - lot sdk.Coin - bid sdk.Coin - } - tests := []struct { - name string - auction ForwardAuction - args args - expectedOutputs []BankOutput - expectedInputs []BankInput - expectedEndTime EndTime - expectedBidder sdk.AccAddress - expectedBid sdk.Coin - expectpass bool - }{ - { - "normal", - ForwardAuction{BaseAuction{ - Initiator: seller, - Lot: c("usdx", 100), - Bidder: buyer1, - Bid: c("kava", 6), - EndTime: end, - MaxEndTime: end, - }}, - args{now, buyer2, c("usdx", 100), c("kava", 10)}, - []BankOutput{{buyer2, c("kava", 10)}}, - []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, - now + DefaultMaxBidDuration, - buyer2, - c("kava", 10), - true, - }, - { - "lowBid", - ForwardAuction{BaseAuction{ - Initiator: seller, - Lot: c("usdx", 100), - Bidder: buyer1, - Bid: c("kava", 6), - EndTime: end, - MaxEndTime: end, - }}, - args{now, buyer2, c("usdx", 100), c("kava", 5)}, - []BankOutput{}, - []BankInput{}, - end, - buyer1, - c("kava", 6), - false, - }, - { - "equalBid", - ForwardAuction{BaseAuction{ - Initiator: seller, - Lot: c("usdx", 100), - Bidder: buyer1, - Bid: c("kava", 6), - EndTime: end, - MaxEndTime: end, - }}, - args{now, buyer2, c("usdx", 100), c("kava", 6)}, - []BankOutput{}, - []BankInput{}, - end, - buyer1, - c("kava", 6), - false, - }, - { - "timeout", - ForwardAuction{BaseAuction{ - Initiator: seller, - Lot: c("usdx", 100), - Bidder: buyer1, - Bid: c("kava", 6), - EndTime: end, - MaxEndTime: end, - }}, - args{end + 1, buyer2, c("usdx", 100), c("kava", 10)}, - []BankOutput{}, - []BankInput{}, - end, - buyer1, - c("kava", 6), - false, - }, - { - "hitMaxEndTime", - ForwardAuction{BaseAuction{ - Initiator: seller, - Lot: c("usdx", 100), - Bidder: buyer1, - Bid: c("kava", 6), - EndTime: end, - MaxEndTime: end, - }}, - args{end - 1, buyer2, c("usdx", 100), c("kava", 10)}, - []BankOutput{{buyer2, c("kava", 10)}}, - []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, - end, // end time should be capped at MaxEndTime - buyer2, - c("kava", 10), - true, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // update auction and return in/outputs - outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) +// func TestReverseAuction_PlaceBid(t *testing.T) { +// buyer := sdk.AccAddress([]byte("a_buyer")) +// seller1 := sdk.AccAddress([]byte("seller1")) +// seller2 := sdk.AccAddress([]byte("seller2")) +// end := EndTime(10000) +// now := EndTime(10) - // check for err - if tc.expectpass { - require.Nil(t, err) - } else { - require.NotNil(t, err) - } - // check for correct in/outputs - require.Equal(t, tc.expectedOutputs, outputs) - require.Equal(t, tc.expectedInputs, inputs) - // check for correct EndTime, bidder, bid - require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) - require.Equal(t, tc.expectedBidder, tc.auction.Bidder) - require.Equal(t, tc.expectedBid, tc.auction.Bid) - }) - } -} +// type args struct { +// currentBlockHeight EndTime +// bidder sdk.AccAddress +// lot sdk.Coin +// bid sdk.Coin +// } +// tests := []struct { +// name string +// auction ReverseAuction +// args args +// expectedOutputs []BankOutput +// expectedInputs []BankInput +// expectedEndTime EndTime +// expectedBidder sdk.AccAddress +// expectedLot sdk.Coin +// expectpass bool +// }{ +// { +// "normal", +// ReverseAuction{BaseAuction{ +// Initiator: buyer, +// Lot: c("kava", 10), +// Bidder: seller1, +// Bid: c("usdx", 100), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, seller2, c("kava", 9), c("usdx", 100)}, +// []BankOutput{{seller2, c("usdx", 100)}}, +// []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, +// now + DefaultMaxBidDuration, +// seller2, +// c("kava", 9), +// true, +// }, +// { +// "highBid", +// ReverseAuction{BaseAuction{ +// Initiator: buyer, +// Lot: c("kava", 10), +// Bidder: seller1, +// Bid: c("usdx", 100), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, seller2, c("kava", 11), c("usdx", 100)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// seller1, +// c("kava", 10), +// false, +// }, +// { +// "equalBid", +// ReverseAuction{BaseAuction{ +// Initiator: buyer, +// Lot: c("kava", 10), +// Bidder: seller1, +// Bid: c("usdx", 100), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{now, seller2, c("kava", 10), c("usdx", 100)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// seller1, +// c("kava", 10), +// false, +// }, +// { +// "timeout", +// ReverseAuction{BaseAuction{ +// Initiator: buyer, +// Lot: c("kava", 10), +// Bidder: seller1, +// Bid: c("usdx", 100), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{end + 1, seller2, c("kava", 9), c("usdx", 100)}, +// []BankOutput{}, +// []BankInput{}, +// end, +// seller1, +// c("kava", 10), +// false, +// }, +// { +// "hitMaxEndTime", +// ReverseAuction{BaseAuction{ +// Initiator: buyer, +// Lot: c("kava", 10), +// Bidder: seller1, +// Bid: c("usdx", 100), +// EndTime: end, +// MaxEndTime: end, +// }}, +// args{end - 1, seller2, c("kava", 9), c("usdx", 100)}, +// []BankOutput{{seller2, c("usdx", 100)}}, +// []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, +// end, // end time should be capped at MaxEndTime +// seller2, +// c("kava", 9), +// true, +// }, +// } +// for _, tc := range tests { +// t.Run(tc.name, func(t *testing.T) { +// // update auction and return in/outputs +// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) -func TestReverseAuction_PlaceBid(t *testing.T) { - buyer := sdk.AccAddress([]byte("a_buyer")) - seller1 := sdk.AccAddress([]byte("seller1")) - seller2 := sdk.AccAddress([]byte("seller2")) - end := EndTime(10000) - now := EndTime(10) +// // check for err +// if tc.expectpass { +// require.Nil(t, err) +// } else { +// require.NotNil(t, err) +// } +// // check for correct in/outputs +// require.Equal(t, tc.expectedOutputs, outputs) +// require.Equal(t, tc.expectedInputs, inputs) +// // check for correct EndTime, bidder, bid +// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) +// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) +// require.Equal(t, tc.expectedLot, tc.auction.Lot) +// }) +// } +// } - type args struct { - currentBlockHeight EndTime - bidder sdk.AccAddress - lot sdk.Coin - bid sdk.Coin - } - tests := []struct { - name string - auction ReverseAuction - args args - expectedOutputs []BankOutput - expectedInputs []BankInput - expectedEndTime EndTime - expectedBidder sdk.AccAddress - expectedLot sdk.Coin - expectpass bool - }{ - { - "normal", - ReverseAuction{BaseAuction{ - Initiator: buyer, - Lot: c("kava", 10), - Bidder: seller1, - Bid: c("usdx", 100), - EndTime: end, - MaxEndTime: end, - }}, - args{now, seller2, c("kava", 9), c("usdx", 100)}, - []BankOutput{{seller2, c("usdx", 100)}}, - []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, - now + DefaultMaxBidDuration, - seller2, - c("kava", 9), - true, - }, - { - "highBid", - ReverseAuction{BaseAuction{ - Initiator: buyer, - Lot: c("kava", 10), - Bidder: seller1, - Bid: c("usdx", 100), - EndTime: end, - MaxEndTime: end, - }}, - args{now, seller2, c("kava", 11), c("usdx", 100)}, - []BankOutput{}, - []BankInput{}, - end, - seller1, - c("kava", 10), - false, - }, - { - "equalBid", - ReverseAuction{BaseAuction{ - Initiator: buyer, - Lot: c("kava", 10), - Bidder: seller1, - Bid: c("usdx", 100), - EndTime: end, - MaxEndTime: end, - }}, - args{now, seller2, c("kava", 10), c("usdx", 100)}, - []BankOutput{}, - []BankInput{}, - end, - seller1, - c("kava", 10), - false, - }, - { - "timeout", - ReverseAuction{BaseAuction{ - Initiator: buyer, - Lot: c("kava", 10), - Bidder: seller1, - Bid: c("usdx", 100), - EndTime: end, - MaxEndTime: end, - }}, - args{end + 1, seller2, c("kava", 9), c("usdx", 100)}, - []BankOutput{}, - []BankInput{}, - end, - seller1, - c("kava", 10), - false, - }, - { - "hitMaxEndTime", - ReverseAuction{BaseAuction{ - Initiator: buyer, - Lot: c("kava", 10), - Bidder: seller1, - Bid: c("usdx", 100), - EndTime: end, - MaxEndTime: end, - }}, - args{end - 1, seller2, c("kava", 9), c("usdx", 100)}, - []BankOutput{{seller2, c("usdx", 100)}}, - []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, - end, // end time should be capped at MaxEndTime - seller2, - c("kava", 9), - true, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // update auction and return in/outputs - outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) +// func TestForwardReverseAuction_PlaceBid(t *testing.T) { +// cdpOwner := sdk.AccAddress([]byte("a_cdp_owner")) +// seller := sdk.AccAddress([]byte("a_seller")) +// buyer1 := sdk.AccAddress([]byte("buyer1")) +// buyer2 := sdk.AccAddress([]byte("buyer2")) +// end := EndTime(10000) +// now := EndTime(10) - // check for err - if tc.expectpass { - require.Nil(t, err) - } else { - require.NotNil(t, err) - } - // check for correct in/outputs - require.Equal(t, tc.expectedOutputs, outputs) - require.Equal(t, tc.expectedInputs, inputs) - // check for correct EndTime, bidder, bid - require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) - require.Equal(t, tc.expectedBidder, tc.auction.Bidder) - require.Equal(t, tc.expectedLot, tc.auction.Lot) - }) - } -} +// type args struct { +// currentBlockHeight EndTime +// bidder sdk.AccAddress +// lot sdk.Coin +// bid sdk.Coin +// } +// tests := []struct { +// name string +// auction ForwardReverseAuction +// args args +// expectedOutputs []BankOutput +// expectedInputs []BankInput +// expectedEndTime EndTime +// expectedBidder sdk.AccAddress +// expectedLot sdk.Coin +// expectedBid sdk.Coin +// expectpass bool +// }{ +// { +// "normalForwardBid", +// ForwardReverseAuction{BaseAuction: BaseAuction{ +// Initiator: seller, +// Lot: c("xrp", 100), +// Bidder: buyer1, +// Bid: c("usdx", 5), +// EndTime: end, +// MaxEndTime: end}, +// MaxBid: c("usdx", 10), +// OtherPerson: cdpOwner, +// }, +// args{now, buyer2, c("xrp", 100), c("usdx", 6)}, +// []BankOutput{{buyer2, c("usdx", 6)}}, +// []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 1)}}, +// now + DefaultMaxBidDuration, +// buyer2, +// c("xrp", 100), +// c("usdx", 6), +// true, +// }, +// { +// "normalSwitchOverBid", +// ForwardReverseAuction{BaseAuction: BaseAuction{ +// Initiator: seller, +// Lot: c("xrp", 100), +// Bidder: buyer1, +// Bid: c("usdx", 5), +// EndTime: end, +// MaxEndTime: end}, +// MaxBid: c("usdx", 10), +// OtherPerson: cdpOwner, +// }, +// args{now, buyer2, c("xrp", 99), c("usdx", 10)}, +// []BankOutput{{buyer2, c("usdx", 10)}}, +// []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 5)}, {cdpOwner, c("xrp", 1)}}, +// now + DefaultMaxBidDuration, +// buyer2, +// c("xrp", 99), +// c("usdx", 10), +// true, +// }, +// { +// "normalReverseBid", +// ForwardReverseAuction{BaseAuction: BaseAuction{ +// Initiator: seller, +// Lot: c("xrp", 99), +// Bidder: buyer1, +// Bid: c("usdx", 10), +// EndTime: end, +// MaxEndTime: end}, +// MaxBid: c("usdx", 10), +// OtherPerson: cdpOwner, +// }, +// args{now, buyer2, c("xrp", 90), c("usdx", 10)}, +// []BankOutput{{buyer2, c("usdx", 10)}}, +// []BankInput{{buyer1, c("usdx", 10)}, {cdpOwner, c("xrp", 9)}}, +// now + DefaultMaxBidDuration, +// buyer2, +// c("xrp", 90), +// c("usdx", 10), +// true, +// }, +// // TODO more test cases +// } +// for _, tc := range tests { +// t.Run(tc.name, func(t *testing.T) { +// // update auction and return in/outputs +// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) -func TestForwardReverseAuction_PlaceBid(t *testing.T) { - cdpOwner := sdk.AccAddress([]byte("a_cdp_owner")) - seller := sdk.AccAddress([]byte("a_seller")) - buyer1 := sdk.AccAddress([]byte("buyer1")) - buyer2 := sdk.AccAddress([]byte("buyer2")) - end := EndTime(10000) - now := EndTime(10) +// // check for err +// if tc.expectpass { +// require.Nil(t, err) +// } else { +// require.NotNil(t, err) +// } +// // check for correct in/outputs +// require.Equal(t, tc.expectedOutputs, outputs) +// require.Equal(t, tc.expectedInputs, inputs) +// // check for correct EndTime, bidder, bid +// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) +// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) +// require.Equal(t, tc.expectedLot, tc.auction.Lot) +// require.Equal(t, tc.expectedBid, tc.auction.Bid) +// }) +// } +// } - type args struct { - currentBlockHeight EndTime - bidder sdk.AccAddress - lot sdk.Coin - bid sdk.Coin - } - tests := []struct { - name string - auction ForwardReverseAuction - args args - expectedOutputs []BankOutput - expectedInputs []BankInput - expectedEndTime EndTime - expectedBidder sdk.AccAddress - expectedLot sdk.Coin - expectedBid sdk.Coin - expectpass bool - }{ - { - "normalForwardBid", - ForwardReverseAuction{BaseAuction: BaseAuction{ - Initiator: seller, - Lot: c("xrp", 100), - Bidder: buyer1, - Bid: c("usdx", 5), - EndTime: end, - MaxEndTime: end}, - MaxBid: c("usdx", 10), - OtherPerson: cdpOwner, - }, - args{now, buyer2, c("xrp", 100), c("usdx", 6)}, - []BankOutput{{buyer2, c("usdx", 6)}}, - []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 1)}}, - now + DefaultMaxBidDuration, - buyer2, - c("xrp", 100), - c("usdx", 6), - true, - }, - { - "normalSwitchOverBid", - ForwardReverseAuction{BaseAuction: BaseAuction{ - Initiator: seller, - Lot: c("xrp", 100), - Bidder: buyer1, - Bid: c("usdx", 5), - EndTime: end, - MaxEndTime: end}, - MaxBid: c("usdx", 10), - OtherPerson: cdpOwner, - }, - args{now, buyer2, c("xrp", 99), c("usdx", 10)}, - []BankOutput{{buyer2, c("usdx", 10)}}, - []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 5)}, {cdpOwner, c("xrp", 1)}}, - now + DefaultMaxBidDuration, - buyer2, - c("xrp", 99), - c("usdx", 10), - true, - }, - { - "normalReverseBid", - ForwardReverseAuction{BaseAuction: BaseAuction{ - Initiator: seller, - Lot: c("xrp", 99), - Bidder: buyer1, - Bid: c("usdx", 10), - EndTime: end, - MaxEndTime: end}, - MaxBid: c("usdx", 10), - OtherPerson: cdpOwner, - }, - args{now, buyer2, c("xrp", 90), c("usdx", 10)}, - []BankOutput{{buyer2, c("usdx", 10)}}, - []BankInput{{buyer1, c("usdx", 10)}, {cdpOwner, c("xrp", 9)}}, - now + DefaultMaxBidDuration, - buyer2, - c("xrp", 90), - c("usdx", 10), - true, - }, - // TODO more test cases - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // update auction and return in/outputs - outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) - - // check for err - if tc.expectpass { - require.Nil(t, err) - } else { - require.NotNil(t, err) - } - // check for correct in/outputs - require.Equal(t, tc.expectedOutputs, outputs) - require.Equal(t, tc.expectedInputs, inputs) - // check for correct EndTime, bidder, bid - require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) - require.Equal(t, tc.expectedBidder, tc.auction.Bidder) - require.Equal(t, tc.expectedLot, tc.auction.Lot) - require.Equal(t, tc.expectedBid, tc.auction.Bid) - }) - } -} - -// defined to avoid cluttering test cases with long function name -func c(denom string, amount int64) sdk.Coin { - return sdk.NewInt64Coin(denom, amount) -} +// // defined to avoid cluttering test cases with long function name +// func c(denom string, amount int64) sdk.Coin { +// return sdk.NewInt64Coin(denom, amount) +// } From 8a4109ff266e814b5871a8e39a4a32c66176b77a Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sat, 28 Dec 2019 22:00:04 +0000 Subject: [PATCH 09/27] update endblocker test --- x/auction/abci_test.go | 38 +++++++++++++++++------- x/auction/alias.go | 2 +- x/auction/doc.go | 14 --------- x/auction/keeper/auctions.go | 17 +++++++---- x/auction/keeper/auctions_test.go | 6 ++-- x/auction/keeper/keeper.go | 1 + x/auction/keeper/keeper_test.go | 48 +++++++++++++++---------------- x/auction/types/params.go | 4 +-- 8 files changed, 70 insertions(+), 60 deletions(-) delete mode 100644 x/auction/doc.go diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 38941aeb..bb6a6585 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -4,40 +4,53 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/auction" + "github.com/kava-labs/kava/x/liquidator" ) func TestKeeper_EndBlocker(t *testing.T) { // Setup - _, addrs := app.GeneratePrivKeyAddressPairs(1) - seller := addrs[0] + _, addrs := app.GeneratePrivKeyAddressPairs(2) + buyer := addrs[0] + recipient := addrs[1] + sellerModName := liquidator.ModuleName + //sellerAddr := supply.NewModuleAddress(sellerModName) tApp := app.NewTestApp() + sellerAcc := supply.NewEmptyModuleAccount(sellerModName) + require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) tApp.InitializeFromGenesisStates( - app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100))}), + NewAuthGenStateFromAccs(authexported.GenesisAccounts{ + auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + sellerAcc, + }), ) ctx := tApp.NewContext(true, abci.Header{}) keeper := tApp.GetAuctionKeeper() - auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) + auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), recipient) require.NoError(t, err) + require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30), c("token1", 20))) - // Run the endblocker, simulating a block height just before auction expiry - preExpiryHeight := ctx.BlockHeight() + int64(auction.DefaultMaxAuctionDuration) - 1 - auction.EndBlocker(ctx.WithBlockHeight(preExpiryHeight), keeper) + // Run the endblocker, simulating a block time 1ns before auction expiry + preExpiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration - 1) + auction.EndBlocker(ctx.WithBlockTime(preExpiryTime), keeper) // Check auction has not been closed yet _, found := keeper.GetAuction(ctx, auctionID) require.True(t, found) - // Run the endblocker, simulating a block height just after auction expiry - expiryHeight := preExpiryHeight + 1 - auction.EndBlocker(ctx.WithBlockHeight(expiryHeight), keeper) + // Run the endblocker, simulating a block time equal to auction expiry + expiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration) + auction.EndBlocker(ctx.WithBlockTime(expiryTime), keeper) // Check auction has been closed _, found = keeper.GetAuction(ctx, auctionID) @@ -46,3 +59,8 @@ func TestKeeper_EndBlocker(t *testing.T) { func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func NewAuthGenStateFromAccs(accounts authexported.GenesisAccounts) app.GenesisState { + authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts) + return app.GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)} +} diff --git a/x/auction/alias.go b/x/auction/alias.go index 799cc625..acf20129 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -16,7 +16,7 @@ const ( RouterKey = types.RouterKey DefaultParamspace = types.DefaultParamspace DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration - DefaultMaxBidDuration = types.DefaultMaxBidDuration + DefaultBidDuration = types.DefaultBidDuration QueryGetAuction = types.QueryGetAuction ) diff --git a/x/auction/doc.go b/x/auction/doc.go deleted file mode 100644 index 79e8def7..00000000 --- a/x/auction/doc.go +++ /dev/null @@ -1,14 +0,0 @@ -/* -Package auction is a module for creating generic auctions and allowing users to place bids until a timeout is reached. - -TODO - - investigate when exactly auctions close and verify queue/endblocker logic is ok - - add more test cases, add stronger validation to user inputs - - add minimum bid increment - - decided whether to put auction params like default timeouts into the auctions themselves - - add docs - - Add constants for the module and route names - - user facing things like cli, rest, querier, tags - - custom error types, codespace -*/ -package auction diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 2b23da74..a1215624 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -9,7 +9,7 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -// StartForwardAuction starts a normal auction. +// StartForwardAuction starts a normal auction that mints the sold coins. func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { // create auction auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) @@ -108,7 +108,12 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr } // store updated auction - k.SetAuction(ctx, a) // TODO maybe move into above funcs + existing, found := k.GetAuction(ctx, a.GetID()) + if found { + k.RemoveFromQueue(ctx, existing.GetEndTime(), existing.GetID()) + } + k.SetAuction(ctx, a) + k.InsertIntoQueue(ctx, a.GetEndTime(), a.GetID()) return nil } @@ -149,7 +154,7 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder a.Bidder = bidder a.Bid = bid // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) // TODO write a min func for time types + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) return a, nil } @@ -211,7 +216,7 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu a.Lot = lot a.Bid = bid // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) return a, nil } @@ -245,7 +250,7 @@ func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder a.Bidder = bidder a.Lot = lot // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) return a, nil } @@ -282,6 +287,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { // Delete auction from store (and queue) k.DeleteAuction(ctx, auctionID) + k.RemoveFromQueue(ctx, auction.GetEndTime(), auction.GetID()) return nil } @@ -297,7 +303,6 @@ func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) return nil } func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { - // TODO this function is responsible for the addition of GetBidder and GetLot to auction interface. Could be split in to two funcs that operate on concrete auction types err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBidder(), sdk.NewCoins(a.GetLot())) if err != nil { return err diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 1cfd254a..77f174e7 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -48,7 +48,7 @@ func TestForwardAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) // Close auction at just at auction expiry time - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // Check buyer's coins increased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) @@ -86,7 +86,7 @@ func TestReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20))) // Close auction at just after auction expiry - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // Check seller's coins increased tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) @@ -138,7 +138,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) // Close auction at just after auction expiry - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxBidDuration)) + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // Check buyer's coins increased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index f91774c9..dfb7dd49 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -66,6 +66,7 @@ func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.I auction = auction.WithID(newAuctionID) k.SetAuction(ctx, auction) + k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) err = k.IncrementNextAuctionID(ctx) if err != nil { diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 2e0918af..f6e9a1d9 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -94,40 +94,40 @@ func TestIterateAuctionsByTime(t *testing.T) { keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) - // create a list of times - queue := []struct { + // setup byTime index + byTimeIndex := []struct { endTime time.Time auctionID types.ID }{ - {time.Date(84, time.January, 1, 0, 0, 0, 0, time.UTC), 34345345}, - {time.Date(98, time.January, 2, 0, 0, 0, 0, time.UTC), 5}, - {time.Date(98, time.January, 2, 13, 5, 0, 0, time.UTC), 6}, - {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 1}, - {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 3}, - {time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 4}, - {time.Date(98, time.January, 2, 16, 0, 0, 1, time.UTC), 0}, // TODO tidy up redundant entries + {time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC), 9999}, // distant past + {time.Date(1998, time.January, 1, 11, 59, 59, 999999999, time.UTC), 1}, // just before cutoff + {time.Date(1998, time.January, 1, 11, 59, 59, 999999999, time.UTC), 2}, // + {time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC), 3}, // equal to cutoff + {time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC), 4}, // + {time.Date(1998, time.January, 1, 12, 0, 0, 1, time.UTC), 5}, // just after cutoff + {time.Date(1998, time.January, 1, 12, 0, 0, 1, time.UTC), 6}, // + {time.Date(9999, time.January, 1, 0, 0, 0, 0, time.UTC), 0}, // distant future } - cutoffTime := time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC) - - var expectedQueue []types.ID - for _, i := range queue { - if i.endTime.After(cutoffTime) { // only append items where endTime ≤ cutoffTime - break - } - expectedQueue = append(expectedQueue, i.auctionID) - } - - // write and read queue - for _, v := range queue { + for _, v := range byTimeIndex { keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID) } - var readQueue []types.ID + + // read out values from index up to a cutoff time and check they are as expected + cutoffTime := time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC) + var expectedIndex []types.ID + for _, v := range byTimeIndex { + if v.endTime.Before(cutoffTime) || v.endTime.Equal(cutoffTime) { // endTime ≤ cutoffTime + expectedIndex = append(expectedIndex, v.auctionID) + } + + } + var readIndex []types.ID keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id types.ID) bool { - readQueue = append(readQueue, id) + readIndex = append(readIndex, id) return false }) - require.Equal(t, expectedQueue, readQueue) + require.Equal(t, expectedIndex, readIndex) } func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID { diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 8f9b59ac..5b7cfdf0 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -13,7 +13,7 @@ const ( // DefaultMaxAuctionDuration max length of auction DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour // DefaultBidDuration how long an auction gets extended when someone bids, roughly 3 hours in blocks - DefaultMaxBidDuration time.Duration = 3 * time.Hour + DefaultBidDuration time.Duration = 3 * time.Hour ) // Parameter keys @@ -43,7 +43,7 @@ func NewAuctionParams(maxAuctionDuration time.Duration, bidDuration time.Duratio func DefaultAuctionParams() AuctionParams { return NewAuctionParams( DefaultMaxAuctionDuration, - DefaultMaxBidDuration, + DefaultBidDuration, ) } From 77bfe11f899ca82fa45de736b9c41affcba0db28 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Tue, 31 Dec 2019 11:10:15 +0000 Subject: [PATCH 10/27] add payout to depositors feature --- app/test_common.go | 5 +- x/auction/abci_test.go | 5 +- x/auction/keeper/auctions.go | 33 +++++++++++-- x/auction/keeper/auctions_test.go | 24 ++++++---- x/auction/keeper/integration_test.go | 7 +++ x/auction/keeper/math.go | 69 ++++++++++++++++++++++++++++ x/auction/keeper/math_test.go | 37 +++++++++++++++ x/auction/types/auctions.go | 34 +++++++++++--- 8 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 x/auction/keeper/math.go create mode 100644 x/auction/keeper/math_test.go diff --git a/app/test_common.go b/app/test_common.go index f6a99036..f25c1c23 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -97,8 +97,9 @@ func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) T } func (tApp TestApp) CheckBalance(t *testing.T, ctx sdk.Context, owner sdk.AccAddress, expectedCoins sdk.Coins) { - actualCoins := tApp.GetAccountKeeper().GetAccount(ctx, owner).GetCoins() - require.Equal(t, expectedCoins, actualCoins) + acc := tApp.GetAccountKeeper().GetAccount(ctx, owner) + require.NotNilf(t, acc, "account with address '%s' doesn't exist", owner) + require.Equal(t, expectedCoins, acc.GetCoins()) } // Create a new auth genesis state from some addresses and coins. The state is returned marshalled into a map. diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index bb6a6585..79f526b5 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -19,7 +19,8 @@ func TestKeeper_EndBlocker(t *testing.T) { // Setup _, addrs := app.GeneratePrivKeyAddressPairs(2) buyer := addrs[0] - recipient := addrs[1] + returnAddrs := addrs[1:] + returnWeights := []sdk.Int{sdk.NewInt(1)} sellerModName := liquidator.ModuleName //sellerAddr := supply.NewModuleAddress(sellerModName) @@ -36,7 +37,7 @@ func TestKeeper_EndBlocker(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) keeper := tApp.GetAuctionKeeper() - auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), recipient) + auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) require.NoError(t, err) require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30), c("token1", 20))) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index a1215624..96d305bd 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -46,12 +46,16 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, } // StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. -func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, otherPerson sdk.AccAddress) (types.ID, sdk.Error) { +func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (types.ID, sdk.Error) { // create auction - auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, otherPerson) + weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) + if err != nil { + return 0, err + } + auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) // take coins from module account - err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { return 0, err } @@ -206,10 +210,17 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu if err != nil { return a, err } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.OtherPerson, sdk.NewCoins(lotDecrement)) + // FIXME paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids + lotPayouts, err := splitCoinIntoWeightedBuckets(lotDecrement, a.LotReturns.Weights) if err != nil { return a, err } + for i, payout := range lotPayouts { + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.LotReturns.Addresses[i], sdk.NewCoins(payout)) + if err != nil { + return a, err + } + } // Update Auction a.Bidder = bidder @@ -318,3 +329,17 @@ func earliestTime(t1, t2 time.Time) time.Time { return t2 // also returned if times are equal } } + +func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, sdk.Error) { + for _, bucket := range buckets { + if bucket.IsNegative() { + return nil, sdk.ErrInternal("cannot split coin into bucket with negative weight") + } + } + amounts := splitIntIntoWeightedBuckets(coin.Amount, buckets) + result := make([]sdk.Coin, len(amounts)) + for i, a := range amounts { + result[i] = sdk.NewCoin(coin.Denom, a) + } + return result, nil +} diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 77f174e7..1bc48650 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/supply" @@ -94,9 +95,10 @@ func TestReverseAuctionBasic(t *testing.T) { func TestForwardReverseAuctionBasic(t *testing.T) { // Setup - _, addrs := app.GeneratePrivKeyAddressPairs(2) + _, addrs := app.GeneratePrivKeyAddressPairs(4) buyer := addrs[0] - recipient := addrs[1] + returnAddrs := addrs[1:] + returnWeights := []sdk.Int{i(30), i(20), i(10)} sellerModName := liquidator.ModuleName sellerAddr := supply.NewModuleAddress(sellerModName) @@ -106,7 +108,9 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.InitializeFromGenesisStates( NewAuthGenStateFromAccs(authexported.GenesisAccounts{ auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), - auth.NewBaseAccount(recipient, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + auth.NewBaseAccount(returnAddrs[0], cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + auth.NewBaseAccount(returnAddrs[1], cs(c("token1", 100), c("token2", 100)), nil, 0, 0), + auth.NewBaseAccount(returnAddrs[2], cs(c("token1", 100), c("token2", 100)), nil, 0, 0), sellerAcc, }), ) @@ -114,7 +118,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { keeper := tApp.GetAuctionKeeper() // Start auction - auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson + auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) // seller, lot, maxBid, otherPerson require.NoError(t, err) // Check seller's coins have decreased tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) @@ -125,8 +129,10 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) // Check seller's coins have increased tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110))) - // Check recipient has not received coins - tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 100), c("token2", 100))) + // Check return addresses have not received coins + for _, ra := range returnAddrs { + tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100))) + } // Place a reverse bid require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot @@ -134,8 +140,10 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) // Check seller's coins have increased tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150))) - // Check "recipient" has received coins - tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) + // Check return addresses have received coins + tApp.CheckBalance(t, ctx, returnAddrs[0], cs(c("token1", 102), c("token2", 100))) + tApp.CheckBalance(t, ctx, returnAddrs[1], cs(c("token1", 102), c("token2", 100))) + tApp.CheckBalance(t, ctx, returnAddrs[2], cs(c("token1", 101), c("token2", 100))) // Close auction at just after auction expiry ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) diff --git a/x/auction/keeper/integration_test.go b/x/auction/keeper/integration_test.go index 3ad2500e..38d9e4ce 100644 --- a/x/auction/keeper/integration_test.go +++ b/x/auction/keeper/integration_test.go @@ -10,6 +10,13 @@ import ( func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } +func i(n int64) sdk.Int { return sdk.NewInt(n) } +func is(ns ...int64) (is []sdk.Int) { + for _, n := range ns { + is = append(is, sdk.NewInt(n)) + } + return +} func NewAuthGenStateFromAccs(accounts authexported.GenesisAccounts) app.GenesisState { authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts) diff --git a/x/auction/keeper/math.go b/x/auction/keeper/math.go new file mode 100644 index 00000000..437b4f22 --- /dev/null +++ b/x/auction/keeper/math.go @@ -0,0 +1,69 @@ +package keeper + +import ( + "sort" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// splitIntIntoWeightedBuckets divides an initial +ve integer among several buckets in proportion to the buckets' weights +// It uses the largest remainder method: +// https://en.wikipedia.org/wiki/Largest_remainder_method +// see also: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100 +func splitIntIntoWeightedBuckets(amount sdk.Int, buckets []sdk.Int) []sdk.Int { + // TODO ideally change algorithm to work with -ve numbers. Limiting to +ve numbers until them + if amount.IsNegative() { + panic("negative amount") + } + for _, bucket := range buckets { + if bucket.IsNegative() { + panic("negative bucket") + } + } + + totalWeights := totalInts(buckets...) + + // split amount by weights, recording whole number part and remainder + quotients := make([]quoRem, len(buckets)) + for i := range buckets { + q := amount.Mul(buckets[i]).Quo(totalWeights) + r := amount.Mul(buckets[i]).Mod(totalWeights) + quotients[i] = quoRem{index: i, quo: q, rem: r} + } + + // apportion left over to buckets with the highest remainder (to minimize error) + sort.Slice(quotients, func(i, j int) bool { + return quotients[i].rem.GT(quotients[j].rem) // decreasing remainder order + }) + + allocated := sdk.ZeroInt() + for _, qr := range quotients { + allocated = allocated.Add(qr.quo) + } + leftToAllocate := amount.Sub(allocated) + + results := make([]sdk.Int, len(quotients)) + for _, qr := range quotients { + results[qr.index] = qr.quo + if !leftToAllocate.IsZero() { + results[qr.index] = results[qr.index].Add(sdk.OneInt()) + leftToAllocate = leftToAllocate.Sub(sdk.OneInt()) + } + } + return results +} + +type quoRem struct { + index int + quo sdk.Int + rem sdk.Int +} + +// totalInts adds together sdk.Ints +func totalInts(is ...sdk.Int) sdk.Int { + total := sdk.ZeroInt() + for _, i := range is { + total = total.Add(i) + } + return total +} diff --git a/x/auction/keeper/math_test.go b/x/auction/keeper/math_test.go new file mode 100644 index 00000000..58e33e7e --- /dev/null +++ b/x/auction/keeper/math_test.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestSplitIntIntoWeightedBuckets(t *testing.T) { + testCases := []struct { + name string + amount sdk.Int + buckets []sdk.Int + want []sdk.Int + }{ + {"2split1,1", i(2), is(1, 1), is(1, 1)}, + {"100split1,9", i(100), is(1, 9), is(10, 90)}, + {"7split1,2", i(7), is(1, 2), is(2, 5)}, + {"17split1,1,1", i(17), is(1, 1, 1), is(6, 6, 5)}, + // TODO more tests + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := splitIntIntoWeightedBuckets(tc.amount, tc.buckets) + require.Equal(t, tc.want, got) + }) + } +} + +func i(n int64) sdk.Int { return sdk.NewInt(n) } +func is(ns ...int64) (is []sdk.Int) { + for _, n := range ns { + is = append(is, sdk.NewInt(n)) + } + return +} diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 9e45ee5c..b293c5ee 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -133,8 +133,8 @@ func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin // ForwardReverseAuction type for forward reverse auction type ForwardReverseAuction struct { BaseAuction - MaxBid sdk.Coin - OtherPerson sdk.AccAddress // TODO rename, this is normally the original CDP owner, will have to be updated to account for deposits + MaxBid sdk.Coin + LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. } // WithID returns an auction with the ID set @@ -149,15 +149,15 @@ func (a ForwardReverseAuction) String() string { End Time: %s Max End Time: %s Max Bid %s - Other Person %s`, + LotReturns %s`, a.GetID(), a.Initiator, a.Lot, a.Bidder, a.Bid, a.GetEndTime().String(), - a.MaxEndTime.String(), a.MaxBid, a.OtherPerson, + a.MaxEndTime.String(), a.MaxBid, a.LotReturns, ) } // NewForwardReverseAuction creates a new forward reverse auction -func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, otherPerson sdk.AccAddress) ForwardReverseAuction { +func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) ForwardReverseAuction { auction := ForwardReverseAuction{ BaseAuction: BaseAuction{ // no ID @@ -167,8 +167,28 @@ func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, ma Bid: sdk.NewInt64Coin(maxBid.Denom, 0), EndTime: EndTime, MaxEndTime: EndTime}, - MaxBid: maxBid, - OtherPerson: otherPerson, + MaxBid: maxBid, + LotReturns: lotReturns, } return auction } + +type WeightedAddresses struct { + Addresses []sdk.AccAddress + Weights []sdk.Int +} + +func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { + if len(addrs) != len(weights) { + return WeightedAddresses{}, sdk.ErrInternal("number of addresses doesn't match number of weights") + } + for _, w := range weights { + if w.IsNegative() { + return WeightedAddresses{}, sdk.ErrInternal("weights contain a negative amount") + } + } + return WeightedAddresses{ + Addresses: addrs, + Weights: weights, + }, nil +} From 4e7f18313a14b07c14f397d4afee68d986d6a2cb Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Tue, 31 Dec 2019 11:10:58 +0000 Subject: [PATCH 11/27] add more tests --- x/auction/keeper/auctions_test.go | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 1bc48650..aad049a3 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -151,3 +152,90 @@ func TestForwardReverseAuctionBasic(t *testing.T) { // Check buyer's coins increased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) } + +func TestStartForwardAuction(t *testing.T) { + someTime := time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC) + type args struct { + seller string + lot sdk.Coin + bidDenom string + } + testCases := []struct { + name string + blockTime time.Time + args args + expectPass bool + }{ + { + "normal", + someTime, + args{liquidator.ModuleName, c("stable", 10), "gov"}, + true, + }, + { + "no module account", + someTime, + args{"nonExistentModule", c("stable", 10), "gov"}, + false, + }, + { + "not enough coins", + someTime, + args{liquidator.ModuleName, c("stable", 101), "gov"}, + false, + }, + { + "incorrect denom", + someTime, + args{liquidator.ModuleName, c("notacoin", 10), "gov"}, + false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // setup + initialLiquidatorCoins := cs(c("stable", 100)) + tApp := app.NewTestApp() + + liqAcc := supply.NewEmptyModuleAccount(liquidator.ModuleName, supply.Burner) // TODO could add test to check for burner permissions + require.NoError(t, liqAcc.SetCoins(initialLiquidatorCoins)) + tApp.InitializeFromGenesisStates( + NewAuthGenStateFromAccs(authexported.GenesisAccounts{liqAcc}), + ) + ctx := tApp.NewContext(false, abci.Header{}).WithBlockTime(tc.blockTime) + keeper := tApp.GetAuctionKeeper() + + // run function under test + id, err := keeper.StartForwardAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) + + // check + sk := tApp.GetSupplyKeeper() + liquidatorCoins := sk.GetModuleAccount(ctx, liquidator.ModuleName).GetCoins() + actualAuc, found := keeper.GetAuction(ctx, id) + + if tc.expectPass { + require.NoError(t, err) + // check coins moved + require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins) + // check auction in store and is correct + require.True(t, found) + expectedAuction := types.Auction(types.ForwardAuction{BaseAuction: types.BaseAuction{ + ID: types.ID(0), + Initiator: tc.args.seller, + Lot: tc.args.lot, + Bidder: nil, + Bid: c(tc.args.bidDenom, 0), + EndTime: tc.blockTime.Add(types.DefaultMaxAuctionDuration), + MaxEndTime: tc.blockTime.Add(types.DefaultMaxAuctionDuration), + }}) + require.Equal(t, expectedAuction, actualAuc) + } else { + require.Error(t, err) + // check coins not moved + require.Equal(t, initialLiquidatorCoins, liquidatorCoins) + // check auction not in store + require.False(t, found) + } + }) + } +} From db3c39aaa525fa40f000daea8bb4edddbd45eed6 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Tue, 31 Dec 2019 11:56:39 +0000 Subject: [PATCH 12/27] move index updates to Get/Set for more safety --- x/auction/keeper/auctions.go | 14 ++---- x/auction/keeper/auctions_test.go | 2 +- x/auction/keeper/keeper.go | 47 ++++++++++---------- x/auction/keeper/keeper_test.go | 74 ++++++++++++++----------------- 4 files changed, 61 insertions(+), 76 deletions(-) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 96d305bd..0ece2e5c 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -20,7 +20,7 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin return 0, err } // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err } @@ -38,7 +38,7 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, return 0, sdk.ErrInternal("module does not have minting permissions") } // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err } @@ -60,7 +60,7 @@ func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot s return 0, err } // store the auction - auctionID, err := k.storeNewAuction(ctx, auction) + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err } @@ -112,13 +112,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddr } // store updated auction - existing, found := k.GetAuction(ctx, a.GetID()) - if found { - k.RemoveFromQueue(ctx, existing.GetEndTime(), existing.GetID()) - } k.SetAuction(ctx, a) - k.InsertIntoQueue(ctx, a.GetEndTime(), a.GetID()) - return nil } @@ -298,8 +292,6 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { // Delete auction from store (and queue) k.DeleteAuction(ctx, auctionID) - k.RemoveFromQueue(ctx, auction.GetEndTime(), auction.GetID()) - return nil } func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) sdk.Error { diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index aad049a3..e3ea8028 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -99,7 +99,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { _, addrs := app.GeneratePrivKeyAddressPairs(4) buyer := addrs[0] returnAddrs := addrs[1:] - returnWeights := []sdk.Int{i(30), i(20), i(10)} + returnWeights := is(30, 20, 10) sellerModName := liquidator.ModuleName sellerAddr := supply.NewModuleAddress(sellerModName) diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index dfb7dd49..d665599b 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -57,8 +57,8 @@ func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { return nil } -// storeNewAuction stores an auction, adding a new ID, and setting indexes -func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { +// 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 @@ -66,7 +66,6 @@ func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.I auction = auction.WithID(newAuctionID) k.SetAuction(ctx, auction) - k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) err = k.IncrementNextAuctionID(ctx) if err != nil { @@ -75,24 +74,22 @@ func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.I return newAuctionID, nil } -// TODO should get/set/delete be responsible for updating auctionByTime index? - // 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 queue if it is already in there - // existingAuction, found := k.GetAuction(ctx, auction.GetID()) - // if found { - // k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) - // } + // 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 the queue - //k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) + // add to index + k.InsertIntoIndex(ctx, auction.GetEndTime(), auction.GetID()) } // getAuction gets an auction from the store by auctionID @@ -111,29 +108,32 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, // DeleteAuction removes an auction from the store without any validation func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { - // remove from queue - //auction, found := k.GetAuction(ctx, auctionID) - // if found { - // k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) - // } + // 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)) } -// Inserts a AuctionID into the queue at endTime -func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { +// 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()) } -// removes an auctionID from the queue -func (k Keeper) RemoveFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { +// 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( @@ -152,9 +152,8 @@ func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time. } } -// IterateAuctions provides an iterator over all stored auctions. For -// each auction, cb will be called. If the cb returns true, the iterator -// will close and stop. +// 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) diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index f6e9a1d9..3667aeb8 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -4,12 +4,10 @@ import ( "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/kava-labs/kava/app" - "github.com/kava-labs/kava/x/auction/keeper" "github.com/kava-labs/kava/x/auction/types" ) @@ -29,10 +27,11 @@ func SetGetDeleteAuction(t *testing.T) { // check before and after match require.True(t, found) require.Equal(t, auction, readAuction) - // check auction is in queue - // iter := keeper.GetQueueIterator(ctx, 100000) - // require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) - // iter.Close() + // check auction is in the index + keeper.IterateAuctionsByTime(ctx, auction.GetEndTime(), func(readID types.ID) bool { + require.Equal(t, auction.GetID(), readID) + return false + }) // delete auction keeper.DeleteAuction(ctx, id) @@ -40,11 +39,11 @@ func SetGetDeleteAuction(t *testing.T) { // check auction does not exist _, found = keeper.GetAuction(ctx, id) require.False(t, found) - // check auction not in queue - // iter = keeper.GetQueueIterator(ctx, 100000) - // require.Equal(t, 0, len(convertIteratorToSlice(keeper, iter))) - // iter.Close() - + // check auction not in index + keeper.IterateAuctionsByTime(ctx, time.Unix(999999999, 0), func(readID types.ID) bool { + require.Fail(t, "index should be empty", " found auction ID '%s", readID) + return false + }) } func TestIncrementNextAuctionID(t *testing.T) { @@ -66,27 +65,32 @@ func TestIncrementNextAuctionID(t *testing.T) { } -// func TestIterateAuctions(t *testing.T) { -// // setup keeper -// tApp := app.NewTestApp() -// keeper := tApp.GetAuctionKeeper() -// ctx := tApp.NewContext(true, abci.Header{}) +func TestIterateAuctions(t *testing.T) { + // setup + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates() + keeper := tApp.GetAuctionKeeper() + ctx := tApp.NewContext(true, abci.Header{}) -// auctions := []types.Auction{ -// &types.ForwardAuction{}, -// } -// for _, a := range auctions { -// keeper.SetAuction(ctx, a) -// } + auctions := []types.Auction{ + types.NewForwardAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0), + types.NewReverseAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(1), + types.NewForwardReverseAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}).WithID(2), + } + for _, a := range auctions { + keeper.SetAuction(ctx, a) + } -// var readAuctions []types.Auction -// keeper.IterateAuctions(ctx, func(a types.Auction) bool { -// readAuctions = append(readAuctions, a) -// return false -// }) + // run + var readAuctions []types.Auction + keeper.IterateAuctions(ctx, func(a types.Auction) bool { + readAuctions = append(readAuctions, a) + return false + }) -// require.Equal(t, auctions, readAuctions) -// } + // check + require.Equal(t, auctions, readAuctions) +} func TestIterateAuctionsByTime(t *testing.T) { // setup keeper @@ -109,7 +113,7 @@ func TestIterateAuctionsByTime(t *testing.T) { {time.Date(9999, time.January, 1, 0, 0, 0, 0, time.UTC), 0}, // distant future } for _, v := range byTimeIndex { - keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID) + keeper.InsertIntoIndex(ctx, v.endTime, v.auctionID) } // read out values from index up to a cutoff time and check they are as expected @@ -129,13 +133,3 @@ func TestIterateAuctionsByTime(t *testing.T) { require.Equal(t, expectedIndex, readIndex) } - -func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID { - var queue []types.ID - for ; iterator.Valid(); iterator.Next() { - var auctionID types.ID - types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auctionID) - queue = append(queue, auctionID) - } - return queue -} From 983de010df54e0bc8a2582a1bc66ac9259676dae Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Wed, 1 Jan 2020 14:11:19 +0000 Subject: [PATCH 13/27] remove slightly unecessary ID type --- x/auction/abci.go | 4 +-- x/auction/alias.go | 2 -- x/auction/client/cli/tx.go | 3 +- x/auction/client/rest/tx.go | 3 +- x/auction/keeper/auctions.go | 10 +++---- x/auction/keeper/auctions_test.go | 2 +- x/auction/keeper/keeper.go | 24 ++++++++-------- x/auction/keeper/keeper_test.go | 16 +++++------ x/auction/types/auctions.go | 38 +++++--------------------- x/auction/types/genesis.go | 6 ++-- x/auction/types/keys.go | 19 ++++++++++--- x/auction/types/msg.go | 4 +-- x/liquidator/keeper/keeper.go | 7 ++--- x/liquidator/types/expected_keepers.go | 7 ++--- 14 files changed, 65 insertions(+), 80 deletions(-) diff --git a/x/auction/abci.go b/x/auction/abci.go index f05a518a..8ce80f90 100644 --- a/x/auction/abci.go +++ b/x/auction/abci.go @@ -7,8 +7,8 @@ import ( // EndBlocker runs at the end of every block. func EndBlocker(ctx sdk.Context, k Keeper) { - var expiredAuctions []ID - k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id ID) bool { + var expiredAuctions []uint64 + k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { expiredAuctions = append(expiredAuctions, id) return false }) diff --git a/x/auction/alias.go b/x/auction/alias.go index acf20129..a3262826 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -22,7 +22,6 @@ const ( var ( // functions aliases - NewIDFromString = types.NewIDFromString NewForwardAuction = types.NewForwardAuction NewReverseAuction = types.NewReverseAuction NewForwardReverseAuction = types.NewForwardReverseAuction @@ -46,7 +45,6 @@ var ( type ( Auction = types.Auction BaseAuction = types.BaseAuction - ID = types.ID ForwardAuction = types.ForwardAuction ReverseAuction = types.ReverseAuction ForwardReverseAuction = types.ForwardReverseAuction diff --git a/x/auction/client/cli/tx.go b/x/auction/client/cli/tx.go index c663658b..2ced6610 100644 --- a/x/auction/client/cli/tx.go +++ b/x/auction/client/cli/tx.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "strconv" "github.com/kava-labs/kava/x/auction/types" "github.com/spf13/cobra" @@ -39,7 +40,7 @@ func GetCmdPlaceBid(cdc *codec.Codec) *cobra.Command { cliCtx := context.NewCLIContext().WithCodec(cdc) txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - id, err := types.NewIDFromString(args[0]) + id, err := strconv.ParseUint(args[0], 10, 64) if err != nil { fmt.Printf("invalid auction id - %s \n", string(args[0])) return err diff --git a/x/auction/client/rest/tx.go b/x/auction/client/rest/tx.go index 61c9e18f..5773f24a 100644 --- a/x/auction/client/rest/tx.go +++ b/x/auction/client/rest/tx.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net/http" + "strconv" "github.com/gorilla/mux" @@ -45,7 +46,7 @@ func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { strBid := vars[restBid] strLot := vars[restLot] - auctionID, err := types.NewIDFromString(strAuctionID) + auctionID, err := strconv.ParseUint(strAuctionID, 10, 64) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 0ece2e5c..51c7c804 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -10,7 +10,7 @@ import ( ) // StartForwardAuction starts a normal auction that mints the sold coins. -func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (types.ID, sdk.Error) { +func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { // create auction auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) @@ -28,7 +28,7 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin } // StartReverseAuction starts an auction where sellers compete by offering decreasing prices. -func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (types.ID, sdk.Error) { +func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { // create auction auction := types.NewReverseAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) @@ -46,7 +46,7 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, } // StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. -func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (types.ID, sdk.Error) { +func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { // create auction weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) if err != nil { @@ -69,7 +69,7 @@ func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot s // PlaceBid places a bid on any auction. // TODO passing bid and lot is weird when only one needed -func (k Keeper) PlaceBid(ctx sdk.Context, auctionID types.ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { +func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { // get auction from store auction, found := k.GetAuction(ctx, auctionID) @@ -261,7 +261,7 @@ func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder } // CloseAuction closes an auction and distributes funds to the highest bidder. -func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { +func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { // get the auction from the store auction, found := k.GetAuction(ctx, auctionID) diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index e3ea8028..c5545476 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -220,7 +220,7 @@ func TestStartForwardAuction(t *testing.T) { // check auction in store and is correct require.True(t, found) expectedAuction := types.Auction(types.ForwardAuction{BaseAuction: types.BaseAuction{ - ID: types.ID(0), + ID: 0, Initiator: tc.args.seller, Lot: tc.args.lot, Bidder: nil, diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index d665599b..28e41540 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -30,21 +30,21 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl } // SetNextAuctionID stores an ID to be used for the next created auction -func (k Keeper) SetNextAuctionID(ctx sdk.Context, id types.ID) { +func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) { store := ctx.KVStore(k.storeKey) - store.Set(types.NextAuctionIDKey, id.Bytes()) + store.Set(types.NextAuctionIDKey, types.Uint64ToBytes(id)) } // 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) { +func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, 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 + return types.Uint64FromBytes(bz), nil } // incrementNextAuctionID increments the global ID in the store by 1 @@ -58,7 +58,7 @@ func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { } // StoreNewAuction stores an auction, adding a new ID -func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { +func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, sdk.Error) { newAuctionID, err := k.GetNextAuctionID(ctx) if err != nil { return 0, err @@ -93,7 +93,7 @@ func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) { } // getAuction gets an auction from the store by auctionID -func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, bool) { +func (k Keeper) GetAuction(ctx sdk.Context, auctionID uint64) (types.Auction, bool) { var auction types.Auction store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) @@ -107,7 +107,7 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, } // DeleteAuction removes an auction from the store without any validation -func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { +func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID uint64) { // remove from index auction, found := k.GetAuction(ctx, auctionID) if found { @@ -120,13 +120,13 @@ func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { } // 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) { +func (k Keeper) InsertIntoIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) - store.Set(types.GetAuctionByTimeKey(endTime, auctionID), auctionID.Bytes()) + store.Set(types.GetAuctionByTimeKey(endTime, auctionID), types.Uint64ToBytes(auctionID)) // TODO } // 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) { +func (k Keeper) RemoveFromIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) } @@ -134,7 +134,7 @@ func (k Keeper) RemoveFromIndex(ctx sdk.Context, endTime time.Time, auctionID ty // 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)) { +func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time.Time, cb func(auctionID uint64) (stop bool)) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) iterator := store.Iterator( nil, // start at the very start of the prefix store @@ -144,7 +144,7 @@ func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time. 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()) + auctionID := types.Uint64FromBytes(iterator.Value()) if cb(auctionID) { break diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 3667aeb8..cc6fbce1 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -17,7 +17,7 @@ func SetGetDeleteAuction(t *testing.T) { keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal - id := types.ID(5) + var id uint64 = 5 auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime).WithID(id) // write and read from store @@ -28,7 +28,7 @@ func SetGetDeleteAuction(t *testing.T) { require.True(t, found) require.Equal(t, auction, readAuction) // check auction is in the index - keeper.IterateAuctionsByTime(ctx, auction.GetEndTime(), func(readID types.ID) bool { + keeper.IterateAuctionsByTime(ctx, auction.GetEndTime(), func(readID uint64) bool { require.Equal(t, auction.GetID(), readID) return false }) @@ -40,7 +40,7 @@ func SetGetDeleteAuction(t *testing.T) { _, found = keeper.GetAuction(ctx, id) require.False(t, found) // check auction not in index - keeper.IterateAuctionsByTime(ctx, time.Unix(999999999, 0), func(readID types.ID) bool { + keeper.IterateAuctionsByTime(ctx, time.Unix(999999999, 0), func(readID uint64) bool { require.Fail(t, "index should be empty", " found auction ID '%s", readID) return false }) @@ -53,7 +53,7 @@ func TestIncrementNextAuctionID(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) // store id - id := types.ID(123456) + var id uint64 = 123456 keeper.SetNextAuctionID(ctx, id) require.NoError(t, keeper.IncrementNextAuctionID(ctx)) @@ -101,7 +101,7 @@ func TestIterateAuctionsByTime(t *testing.T) { // setup byTime index byTimeIndex := []struct { endTime time.Time - auctionID types.ID + auctionID uint64 }{ {time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC), 9999}, // distant past {time.Date(1998, time.January, 1, 11, 59, 59, 999999999, time.UTC), 1}, // just before cutoff @@ -118,15 +118,15 @@ func TestIterateAuctionsByTime(t *testing.T) { // read out values from index up to a cutoff time and check they are as expected cutoffTime := time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC) - var expectedIndex []types.ID + var expectedIndex []uint64 for _, v := range byTimeIndex { if v.endTime.Before(cutoffTime) || v.endTime.Equal(cutoffTime) { // endTime ≤ cutoffTime expectedIndex = append(expectedIndex, v.auctionID) } } - var readIndex []types.ID - keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id types.ID) bool { + var readIndex []uint64 + keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id uint64) bool { readIndex = append(readIndex, id) return false }) diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index b293c5ee..5b9b079f 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -1,41 +1,17 @@ package types import ( - "encoding/binary" "fmt" - "strconv" "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/supply" ) -// ID type for auction IDs -type ID uint64 - -// TODO can this be removed? -// NewIDFromString generate new auction ID from a string -func NewIDFromString(s string) (ID, error) { - n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs - if err != nil { - return 0, err - } - return ID(n), nil -} -func NewIDFromBytes(bz []byte) ID { - return ID(binary.BigEndian.Uint64(bz)) - -} -func (id ID) Bytes() []byte { - bz := make([]byte, 8) - binary.BigEndian.PutUint64(bz, uint64(id)) - return bz -} - // Auction is an interface to several types of auction. type Auction interface { - GetID() ID - WithID(ID) Auction + GetID() uint64 + WithID(uint64) Auction GetBidder() sdk.AccAddress GetBid() sdk.Coin GetLot() sdk.Coin @@ -44,7 +20,7 @@ type Auction interface { // BaseAuction type shared by all Auctions type BaseAuction struct { - ID ID + ID uint64 Initiator string // Module that starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) @@ -54,7 +30,7 @@ type BaseAuction struct { } // GetID getter for auction ID -func (a BaseAuction) GetID() ID { return a.ID } +func (a BaseAuction) GetID() uint64 { return a.ID } // GetBid getter for auction bid func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } @@ -88,7 +64,7 @@ type ForwardAuction struct { } // WithID returns an auction with the ID set -func (a ForwardAuction) WithID(id ID) Auction { a.ID = id; return a } +func (a ForwardAuction) WithID(id uint64) Auction { a.ID = id; return a } // NewForwardAuction creates a new forward auction func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) ForwardAuction { @@ -110,7 +86,7 @@ type ReverseAuction struct { } // WithID returns an auction with the ID set -func (a ReverseAuction) WithID(id ID) Auction { a.ID = id; return a } +func (a ReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } // NewReverseAuction creates a new reverse auction func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) ReverseAuction { @@ -138,7 +114,7 @@ type ForwardReverseAuction struct { } // WithID returns an auction with the ID set -func (a ForwardReverseAuction) WithID(id ID) Auction { a.ID = id; return a } +func (a ForwardReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } func (a ForwardReverseAuction) String() string { return fmt.Sprintf(`Auction %d: diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go index 15530961..27f9eb52 100644 --- a/x/auction/types/genesis.go +++ b/x/auction/types/genesis.go @@ -9,13 +9,13 @@ type GenesisAuctions []Auction // GenesisState - auction state that must be provided at genesis type GenesisState struct { - NextAuctionID ID + NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` AuctionParams AuctionParams `json:"auction_params" yaml:"auction_params"` Auctions GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"` } // NewGenesisState returns a new genesis state object for auctions module -func NewGenesisState(nextID ID, ap AuctionParams, ga GenesisAuctions) GenesisState { +func NewGenesisState(nextID uint64, ap AuctionParams, ga GenesisAuctions) GenesisState { return GenesisState{ NextAuctionID: nextID, AuctionParams: ap, @@ -25,7 +25,7 @@ func NewGenesisState(nextID ID, ap AuctionParams, ga GenesisAuctions) GenesisSta // DefaultGenesisState defines default genesis state for auction module func DefaultGenesisState() GenesisState { - return NewGenesisState(ID(0), DefaultAuctionParams(), GenesisAuctions{}) + return NewGenesisState(0, DefaultAuctionParams(), GenesisAuctions{}) } // Equal checks whether two GenesisState structs are equivalent diff --git a/x/auction/types/keys.go b/x/auction/types/keys.go index fc20ef14..e63bdf54 100644 --- a/x/auction/types/keys.go +++ b/x/auction/types/keys.go @@ -1,6 +1,7 @@ package types import ( + "encoding/binary" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -28,10 +29,20 @@ var ( NextAuctionIDKey = []byte{0x02} ) -func GetAuctionKey(auctionID ID) []byte { - return auctionID.Bytes() +func GetAuctionKey(auctionID uint64) []byte { + return Uint64ToBytes(auctionID) } -func GetAuctionByTimeKey(endTime time.Time, auctionID ID) []byte { - return append(sdk.FormatTimeBytes(endTime), auctionID.Bytes()...) +func GetAuctionByTimeKey(endTime time.Time, auctionID uint64) []byte { + return append(sdk.FormatTimeBytes(endTime), Uint64ToBytes(auctionID)...) } + +func Uint64FromBytes(bz []byte) uint64 { + return binary.BigEndian.Uint64(bz) +} + +func Uint64ToBytes(id uint64) []byte { + bz := make([]byte, 8) + binary.BigEndian.PutUint64(bz, uint64(id)) + return bz +} \ No newline at end of file diff --git a/x/auction/types/msg.go b/x/auction/types/msg.go index 5cdc60e3..0014d01e 100644 --- a/x/auction/types/msg.go +++ b/x/auction/types/msg.go @@ -4,14 +4,14 @@ import sdk "github.com/cosmos/cosmos-sdk/types" // MsgPlaceBid is the message type used to place a bid on any type of auction. type MsgPlaceBid struct { - AuctionID ID + AuctionID uint64 Bidder sdk.AccAddress // This can be a buyer (who increments bid), or a seller (who decrements lot) TODO rename to be clearer? Bid sdk.Coin Lot sdk.Coin } // NewMsgPlaceBid returns a new MsgPlaceBid. -func NewMsgPlaceBid(auctionID ID, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) MsgPlaceBid { +func NewMsgPlaceBid(auctionID uint64, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) MsgPlaceBid { return MsgPlaceBid{ AuctionID: auctionID, Bidder: bidder, diff --git a/x/liquidator/keeper/keeper.go b/x/liquidator/keeper/keeper.go index 59354270..5134d47e 100644 --- a/x/liquidator/keeper/keeper.go +++ b/x/liquidator/keeper/keeper.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" - "github.com/kava-labs/kava/x/auction" "github.com/kava-labs/kava/x/liquidator/types" ) @@ -33,7 +32,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramstore subspace.Subs // SeizeAndStartCollateralAuction pulls collateral out of a CDP and sells it in an auction for stable coin. Excess collateral goes to the original CDP owner. // Known as Cat.bite in maker // result: stable coin is transferred to module account, collateral is transferred from module account to buyer, (and any excess collateral is transferred to original CDP owner) -func (k Keeper) SeizeAndStartCollateralAuction(ctx sdk.Context, owner sdk.AccAddress, collateralDenom string) (auction.ID, sdk.Error) { +func (k Keeper) SeizeAndStartCollateralAuction(ctx sdk.Context, owner sdk.AccAddress, collateralDenom string) (uint64, sdk.Error) { // Get CDP cdp, found := k.cdpKeeper.GetCDP(ctx, owner, collateralDenom) if !found { @@ -73,7 +72,7 @@ func (k Keeper) SeizeAndStartCollateralAuction(ctx sdk.Context, owner sdk.AccAdd // StartDebtAuction sells off minted gov coin to raise set amounts of stable coin. // Known as Vow.flop in maker // result: minted gov coin moved to highest bidder, stable coin moved to moduleAccount -func (k Keeper) StartDebtAuction(ctx sdk.Context) (auction.ID, sdk.Error) { +func (k Keeper) StartDebtAuction(ctx sdk.Context) (uint64, sdk.Error) { // Ensure amount of seized stable coin is 0 (ie Joy = 0) stableCoins := k.bankKeeper.GetCoins(ctx, k.cdpKeeper.GetLiquidatorAccountAddress()).AmountOf(k.cdpKeeper.GetStableDenom()) @@ -107,7 +106,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context) (auction.ID, sdk.Error) { // StartSurplusAuction sells off excess stable coin in exchange for gov coin, which is burned // Known as Vow.flap in maker // result: stable coin removed from module account (eventually to buyer), gov coin transferred to module account -// func (k Keeper) StartSurplusAuction(ctx sdk.Context) (auction.ID, sdk.Error) { +// func (k Keeper) StartSurplusAuction(ctx sdk.Context) (uint64, sdk.Error) { // // TODO ensure seized debt is 0 diff --git a/x/liquidator/types/expected_keepers.go b/x/liquidator/types/expected_keepers.go index deea2bd4..39bede95 100644 --- a/x/liquidator/types/expected_keepers.go +++ b/x/liquidator/types/expected_keepers.go @@ -3,7 +3,6 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/auction" "github.com/kava-labs/kava/x/cdp" ) @@ -26,7 +25,7 @@ type BankKeeper interface { // AuctionKeeper expected interface for the auction keeper type AuctionKeeper interface { - StartForwardAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin) (auction.ID, sdk.Error) - StartReverseAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin) (auction.ID, sdk.Error) - StartForwardReverseAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin, sdk.AccAddress) (auction.ID, sdk.Error) + StartForwardAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin) (uint64, sdk.Error) + StartReverseAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin) (uint64, sdk.Error) + StartForwardReverseAuction(sdk.Context, sdk.AccAddress, sdk.Coin, sdk.Coin, sdk.AccAddress) (uint64, sdk.Error) } From b2fa8d81eecc2951bfb0d53cc421d0b28912553c Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Mon, 6 Jan 2020 16:35:50 +0000 Subject: [PATCH 14/27] remove unused message types --- x/auction/types/genesis.go | 2 +- x/auction/types/msg.go | 47 ++++---------------------------------- 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go index 27f9eb52..34514250 100644 --- a/x/auction/types/genesis.go +++ b/x/auction/types/genesis.go @@ -9,7 +9,7 @@ type GenesisAuctions []Auction // GenesisState - auction state that must be provided at genesis type GenesisState struct { - NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` + NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` AuctionParams AuctionParams `json:"auction_params" yaml:"auction_params"` Auctions GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"` } diff --git a/x/auction/types/msg.go b/x/auction/types/msg.go index 0014d01e..45c10472 100644 --- a/x/auction/types/msg.go +++ b/x/auction/types/msg.go @@ -2,6 +2,9 @@ package types import sdk "github.com/cosmos/cosmos-sdk/types" +// ensure Msg interface compliance at compile time +var _ sdk.Msg = &MsgPlaceBid{} + // MsgPlaceBid is the message type used to place a bid on any type of auction. type MsgPlaceBid struct { AuctionID uint64 @@ -21,7 +24,7 @@ func NewMsgPlaceBid(auctionID uint64, bidder sdk.AccAddress, bid sdk.Coin, lot s } // Route return the message type used for routing the message. -func (msg MsgPlaceBid) Route() string { return "auction" } +func (msg MsgPlaceBid) Route() string { return RouterKey } // Type returns a human-readable string for the message, intended for utilization within tags. func (msg MsgPlaceBid) Type() string { return "place_bid" } @@ -50,44 +53,4 @@ func (msg MsgPlaceBid) GetSignBytes() []byte { // GetSigners returns the addresses of signers that must sign. func (msg MsgPlaceBid) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Bidder} -} - -// The CDP system doesn't need Msgs for starting auctions. But they could be added to allow people to create random auctions of their own, and to make this module more general purpose. - -// type MsgStartForwardAuction struct { -// Seller sdk.AccAddress -// Amount sdk.Coins -// // TODO add starting bid amount? -// // TODO specify asset denom to be received -// } - -// // NewMsgStartAuction returns a new MsgStartAuction. -// func NewMsgStartAuction(seller sdk.AccAddress, amount sdk.Coins, maxBid sdk.Coins) MsgStartAuction { -// return MsgStartAuction{ -// Seller: seller, -// Amount: amount, -// MaxBid: maxBid, -// } -// } - -// // Route return the message type used for routing the message. -// func (msg MsgStartAuction) Route() string { return "auction" } - -// // Type returns a human-readable string for the message, intended for utilization within tags. -// func (msg MsgStartAuction) Type() string { return "start_auction" } - -// // ValidateBasic does a simple validation check that doesn't require access to any other information. -// func (msg MsgStartAuction) ValidateBasic() sdk.Error { -// return nil -// } - -// // GetSignBytes gets the canonical byte representation of the Msg. -// func (msg MsgStartAuction) GetSignBytes() []byte { -// bz := msgCdc.MustMarshalJSON(msg) -// return sdk.MustSortJSON(bz) -// } - -// // GetSigners returns the addresses of signers that must sign. -// func (msg MsgStartAuction) GetSigners() []sdk.AccAddress { -// return []sdk.AccAddress{msg.Seller} -// } +} \ No newline at end of file From de4f55ea20e41d5b39887dca3a8b3929e4e4655f Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Tue, 7 Jan 2020 12:17:27 -0500 Subject: [PATCH 15/27] feat: add spec, update redundant type names --- x/auction/alias.go | 24 +++++++--- x/auction/genesis.go | 4 +- x/auction/keeper/auctions_test.go | 6 ++- x/auction/keeper/params.go | 6 +-- x/auction/spec/01_concepts.md | 13 ++++++ x/auction/spec/02_state.md | 76 +++++++++++++++++++++++++++++++ x/auction/spec/03_messages.md | 37 +++++++++++++++ x/auction/spec/04_events.md | 9 ++++ x/auction/spec/05_params.md | 12 +++++ x/auction/spec/06_begin_block.md | 20 ++++++++ x/auction/spec/README.md | 20 ++++++++ x/auction/types/auctions.go | 1 + x/auction/types/genesis.go | 16 +++---- x/auction/types/params.go | 36 +++++++-------- 14 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 x/auction/spec/01_concepts.md create mode 100644 x/auction/spec/02_state.md create mode 100644 x/auction/spec/03_messages.md create mode 100644 x/auction/spec/04_events.md create mode 100644 x/auction/spec/05_params.md create mode 100644 x/auction/spec/06_begin_block.md create mode 100644 x/auction/spec/README.md diff --git a/x/auction/alias.go b/x/auction/alias.go index a3262826..ec4ad50f 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -25,21 +25,29 @@ var ( NewForwardAuction = types.NewForwardAuction NewReverseAuction = types.NewReverseAuction NewForwardReverseAuction = types.NewForwardReverseAuction + NewWeightedAddresses = types.NewWeightedAddresses RegisterCodec = types.RegisterCodec NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState ValidateGenesis = types.ValidateGenesis + GetAuctionKey = types.GetAuctionKey + GetAuctionByTimeKey = types.GetAuctionByTimeKey + Uint64FromBytes = types.Uint64FromBytes + Uint64ToBytes = types.Uint64ToBytes NewMsgPlaceBid = types.NewMsgPlaceBid - NewAuctionParams = types.NewAuctionParams - DefaultAuctionParams = types.DefaultAuctionParams + NewParams = types.NewParams + DefaultParams = types.DefaultParams ParamKeyTable = types.ParamKeyTable NewKeeper = keeper.NewKeeper NewQuerier = keeper.NewQuerier // variable aliases - ModuleCdc = types.ModuleCdc - KeyAuctionBidDuration = types.KeyAuctionBidDuration - KeyAuctionDuration = types.KeyAuctionDuration + ModuleCdc = types.ModuleCdc + AuctionKeyPrefix = types.AuctionKeyPrefix + AuctionByTimeKeyPrefix = types.AuctionByTimeKeyPrefix + NextAuctionIDKey = types.NextAuctionIDKey + KeyAuctionBidDuration = types.KeyAuctionBidDuration + KeyAuctionDuration = types.KeyAuctionDuration ) type ( @@ -48,10 +56,12 @@ type ( ForwardAuction = types.ForwardAuction ReverseAuction = types.ReverseAuction ForwardReverseAuction = types.ForwardReverseAuction - GenesisAuctions = types.GenesisAuctions + WeightedAddresses = types.WeightedAddresses + SupplyKeeper = types.SupplyKeeper + Auctions = types.Auctions GenesisState = types.GenesisState MsgPlaceBid = types.MsgPlaceBid - AuctionParams = types.AuctionParams + Params = types.Params QueryResAuctions = types.QueryResAuctions Keeper = keeper.Keeper ) diff --git a/x/auction/genesis.go b/x/auction/genesis.go index 6136df9e..8b4a5f1e 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -8,7 +8,7 @@ import ( func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetNextAuctionID(ctx, data.NextAuctionID) - keeper.SetParams(ctx, data.AuctionParams) + keeper.SetParams(ctx, data.Params) for _, a := range data.Auctions { keeper.SetAuction(ctx, a) @@ -24,7 +24,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { params := keeper.GetParams(ctx) - var genAuctions GenesisAuctions + var genAuctions Auctions keeper.IterateAuctions(ctx, func(a Auction) bool { genAuctions = append(genAuctions, a) return false diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index c5545476..696c3382 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -49,11 +49,15 @@ func TestForwardAuctionBasic(t *testing.T) { // Check seller's coins have not increased (because proceeds are burned) tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) + // increment bid same bidder + err = keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 20), c("token1", 20)) + require.NoError(t, err) + // Close auction at just at auction expiry time ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // Check buyer's coins increased - tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 80))) } func TestReverseAuctionBasic(t *testing.T) { diff --git a/x/auction/keeper/params.go b/x/auction/keeper/params.go index c832caf5..bd2d67de 100644 --- a/x/auction/keeper/params.go +++ b/x/auction/keeper/params.go @@ -6,12 +6,12 @@ import ( ) // SetParams sets the auth module's parameters. -func (k Keeper) SetParams(ctx sdk.Context, params types.AuctionParams) { +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSubspace.SetParamSet(ctx, ¶ms) } // GetParams gets the auth module's parameters. -func (k Keeper) GetParams(ctx sdk.Context) (params types.AuctionParams) { +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { k.paramSubspace.GetParamSet(ctx, ¶ms) return -} \ No newline at end of file +} diff --git a/x/auction/spec/01_concepts.md b/x/auction/spec/01_concepts.md new file mode 100644 index 00000000..f656b615 --- /dev/null +++ b/x/auction/spec/01_concepts.md @@ -0,0 +1,13 @@ + + +# Concepts + +Auctions are broken down into three distinct types, which correspond to three specific functionalities within the CDP system. + +* **Forward Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a forward auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, forward auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX. +* **Reverse Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, reverse auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA. +* **Forward Reverse Auction:** An two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example. forward reverse auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM. + +Auctions are always initiated by another module, and not directly by users. Auctions start with an expiry, the time at which the auction is guaranteed to end, even if there have been no bidders. After each bid, the auction is extended by a specific amount of time, `BidDuration`. In the case that increasing the auction time by `BidDuration` would cause the auction to go past its expiry, the expiry is chosen as the ending time. diff --git a/x/auction/spec/02_state.md b/x/auction/spec/02_state.md new file mode 100644 index 00000000..a1c39f84 --- /dev/null +++ b/x/auction/spec/02_state.md @@ -0,0 +1,76 @@ + + +# State + +## Parameters and genesis state + +`Paramaters` define the rules according to which auctions are run. There is only one active parameter set at any given time. Updates to the parameter set can be made via on-chain parameter update proposals. + +```go +// Params governance parameters for auction module +type Params struct { + MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction + MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. +} +``` + +`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the auction module to resume. + +```go +// GenesisState - auction state that must be provided at genesis +type GenesisState struct { + NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` // auctionID that will be used for the next created auction + Params Params `json:"auction_params" yaml:"auction_params"` // auction params + Auctions Auctions `json:"genesis_auctions" yaml:"genesis_auctions"` // auctions currently in the store +} +``` + +## Base types + +```go +// Auction is an interface to several types of auction. +type Auction interface { + GetID() uint64 + WithID(uint64) Auction + GetBidder() sdk.AccAddress + GetBid() sdk.Coin + GetLot() sdk.Coin + GetEndTime() time.Time +} + +// BaseAuction type shared by all Auctions +type BaseAuction struct { + ID uint64 + Initiator string // Module that starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. + Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) + Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) + Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) + EndTime time.Time // Auction closing time. Triggers at the end of the block with time ≥ endTime (bids placed in that block are valid) // TODO ensure everything is consistent with this + MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. +} + +// ForwardAuction type for forward auctions +type ForwardAuction struct { + BaseAuction +} + +// ReverseAuction type for reverse auctions +type ReverseAuction struct { + BaseAuction +} + +// WeightedAddresses type for storing an address and its associated weight +type WeightedAddresses struct { + Addresses []sdk.AccAddress + Weights []sdk.Int +} + +// ForwardReverseAuction type for forward reverse auction +type ForwardReverseAuction struct { + BaseAuction + MaxBid sdk.Coin + LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. +} +``` diff --git a/x/auction/spec/03_messages.md b/x/auction/spec/03_messages.md new file mode 100644 index 00000000..5c8ece91 --- /dev/null +++ b/x/auction/spec/03_messages.md @@ -0,0 +1,37 @@ + + +# Messages + +## Bidding + +Users can bid on auctions using the `MsgPlaceBid` message type. All auction types can be bid on using the same message type. + +```go +// MsgPlaceBid is the message type used to place a bid on any type of auction. +type MsgPlaceBid struct { + AuctionID uint64 + Bidder sdk.AccAddress + Bid sdk.Coin + Lot sdk.Coin +} +``` + +**State Modifications:** + +* Update bidder if different than previous bidder +* For forward auctions: + * Update Bid Amount + * Return bid coins to previous bidder + * Burn coins equal to the increment in the bid (CurrentBid - PreviousBid) +* For Reverse auctions: + * Update lot amount + * Return bid coins to previous bidder +* For Forward Reverse auctions: + * Return bid coins to previous bidder + * If in forward phase: + * Update bid amount + * If in reverse phase: + * Update lot amount +* Extend auction by `BidDuration`, or `MaxEndTime` diff --git a/x/auction/spec/04_events.md b/x/auction/spec/04_events.md new file mode 100644 index 00000000..3bc15051 --- /dev/null +++ b/x/auction/spec/04_events.md @@ -0,0 +1,9 @@ + + +# Events + + diff --git a/x/auction/spec/05_params.md b/x/auction/spec/05_params.md new file mode 100644 index 00000000..621d26aa --- /dev/null +++ b/x/auction/spec/05_params.md @@ -0,0 +1,12 @@ + + +# Parameters + +The auction module contains the following parameters: + +| Key | Type | Example | +| ------------------ | ---------------------- | -----------| +| MaxAuctionDuration | string (time.Duration) | "48h0m0s" | +| MaxBidDuration | string (time.Duration) | "3h0m0s" | diff --git a/x/auction/spec/06_begin_block.md b/x/auction/spec/06_begin_block.md new file mode 100644 index 00000000..2ecfcd54 --- /dev/null +++ b/x/auction/spec/06_begin_block.md @@ -0,0 +1,20 @@ + + +At the beginning of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows: + +```go +var expiredAuctions []uint64 + k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { + expiredAuctions = append(expiredAuctions, id) + return false + }) + + for _, id := range expiredAuctions { + err := k.CloseAuction(ctx, id) + if err != nil { + panic(err) + } + } +``` diff --git a/x/auction/spec/README.md b/x/auction/spec/README.md new file mode 100644 index 00000000..c68ab587 --- /dev/null +++ b/x/auction/spec/README.md @@ -0,0 +1,20 @@ + + +# `auction` + + +1. **[Concepts](01_concepts.md)** +2. **[State](02_state.md)** +3. **[Messages](03_messages.md)** +4. **[Events](04_events.md)** +5. **[Params](05_params.md)** +6. **[BeginBlock](06_begin_block.md)** + +## Abstract + +`x/auction` is an implementation of a Cosmos SDK Module that handles the creation, bidding, and payout of 3 distinct auction types. All auction types implement the `Auction` interface. Each auction type is used at different points during the normal functioning of the CDP system. diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 5b9b079f..1aafaa36 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -149,6 +149,7 @@ func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, ma return auction } +// WeightedAddresses type for storing an address and its associated weight type WeightedAddresses struct { Addresses []sdk.AccAddress Weights []sdk.Int diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go index 34514250..423d12fd 100644 --- a/x/auction/types/genesis.go +++ b/x/auction/types/genesis.go @@ -4,28 +4,28 @@ import ( "bytes" ) -// GenesisAuctions type for an array of auctions -type GenesisAuctions []Auction +// Auctions type for an array of auctions +type Auctions []Auction // GenesisState - auction state that must be provided at genesis type GenesisState struct { NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` - AuctionParams AuctionParams `json:"auction_params" yaml:"auction_params"` - Auctions GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"` + Params Params `json:"auction_params" yaml:"auction_params"` + Auctions Auctions `json:"genesis_auctions" yaml:"genesis_auctions"` } // NewGenesisState returns a new genesis state object for auctions module -func NewGenesisState(nextID uint64, ap AuctionParams, ga GenesisAuctions) GenesisState { +func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState { return GenesisState{ NextAuctionID: nextID, - AuctionParams: ap, + Params: ap, Auctions: ga, } } // DefaultGenesisState defines default genesis state for auction module func DefaultGenesisState() GenesisState { - return NewGenesisState(0, DefaultAuctionParams(), GenesisAuctions{}) + return NewGenesisState(0, DefaultParams(), Auctions{}) } // Equal checks whether two GenesisState structs are equivalent @@ -42,7 +42,7 @@ func (data GenesisState) IsEmpty() bool { // ValidateGenesis validates genesis inputs. Returns error if validation of any input fails. func ValidateGenesis(data GenesisState) error { - if err := data.AuctionParams.Validate(); err != nil { + if err := data.Params.Validate(); err != nil { return err } return nil diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 5b7cfdf0..1aa0276d 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -18,30 +18,30 @@ const ( // Parameter keys var ( - // ParamStoreKeyAuctionParams Param store key for auction params + // ParamStoreKeyParams Param store key for auction params KeyAuctionBidDuration = []byte("MaxBidDuration") KeyAuctionDuration = []byte("MaxAuctionDuration") ) -var _ subspace.ParamSet = &AuctionParams{} +var _ subspace.ParamSet = &Params{} -// AuctionParams governance parameters for auction module -type AuctionParams struct { - MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction, in blocks - MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` +// Params governance parameters for auction module +type Params struct { + MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction + MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. } -// NewAuctionParams creates a new AuctionParams object -func NewAuctionParams(maxAuctionDuration time.Duration, bidDuration time.Duration) AuctionParams { - return AuctionParams{ +// NewParams creates a new Params object +func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params { + return Params{ MaxAuctionDuration: maxAuctionDuration, MaxBidDuration: bidDuration, } } -// DefaultAuctionParams default parameters for auctions -func DefaultAuctionParams() AuctionParams { - return NewAuctionParams( +// DefaultParams default parameters for auctions +func DefaultParams() Params { + return NewParams( DefaultMaxAuctionDuration, DefaultBidDuration, ) @@ -49,35 +49,35 @@ func DefaultAuctionParams() AuctionParams { // ParamKeyTable Key declaration for parameters func ParamKeyTable() subspace.KeyTable { - return subspace.NewKeyTable().RegisterParamSet(&AuctionParams{}) + return subspace.NewKeyTable().RegisterParamSet(&Params{}) } // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs // pairs of auth module's parameters. // nolint -func (ap *AuctionParams) ParamSetPairs() subspace.ParamSetPairs { +func (ap *Params) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ {KeyAuctionBidDuration, &ap.MaxBidDuration}, {KeyAuctionDuration, &ap.MaxAuctionDuration}, } } -// Equal returns a boolean determining if two AuctionParams types are identical. -func (ap AuctionParams) Equal(ap2 AuctionParams) bool { +// Equal returns a boolean determining if two Params types are identical. +func (ap Params) Equal(ap2 Params) bool { bz1 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&ap) bz2 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&ap2) return bytes.Equal(bz1, bz2) } // String implements stringer interface -func (ap AuctionParams) String() string { +func (ap Params) String() string { return fmt.Sprintf(`Auction Params: Max Auction Duration: %s Max Bid Duration: %s`, ap.MaxAuctionDuration, ap.MaxBidDuration) } // Validate checks that the parameters have valid values. -func (ap AuctionParams) Validate() error { +func (ap Params) Validate() error { // TODO check durations are within acceptable limits, if needed return nil } From 3a7cb7e4f6de01a1cadea8992309113f2bfd8d74 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 13:55:45 +0000 Subject: [PATCH 16/27] stop sending zero coins --- x/auction/keeper/auctions.go | 158 ++++++++++++++++-------------- x/auction/keeper/auctions_test.go | 3 +- x/auction/types/auctions.go | 15 +-- x/auction/types/params.go | 4 +- 4 files changed, 100 insertions(+), 80 deletions(-) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 51c7c804..38108079 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -34,7 +34,7 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) - if !macc.HasPermission(supply.Minter) { // TODO ideally don't want to import supply + if !macc.HasPermission(supply.Minter) { return 0, sdk.ErrInternal("module does not have minting permissions") } // store the auction @@ -103,7 +103,11 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres return err } case types.ForwardReverseAuction: - a, err = k.PlaceBidForwardReverse(ctx, auc, bidder, bid, lot) + if !auc.IsReversePhase() { + a, err = k.PlaceBidForwardReverseForward(ctx, auc, bidder, bid) + } else { + a, err = k.PlaceBidForwardReverseReverse(ctx, auc, bidder, lot) + } if err != nil { return err } @@ -126,24 +130,23 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder } // Move Coins - increment := bid.Sub(a.Bid) - bidAmtToReturn := a.Bid - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) + if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { // catch edge case of someone updating their bid with a low balance, also don't send if amt is zero + // pay back previous bidder + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(increment))) + // burn increase in bid + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) if err != nil { return a, err } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(increment)) // increase in bid size is burned - if err != nil { - return a, err - } - err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(increment)) + err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) if err != nil { return a, err } @@ -156,56 +159,71 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder return a, nil } -func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { - // Validate New Bid // TODO min bid increments, make validation code less confusing - if !a.Bid.IsEqual(a.MaxBid) { - // Auction is in forward phase, a bid here can put the auction into forward or reverse phases - if !a.Bid.IsLT(bid) { - return a, sdk.ErrInternal("auction in forward phase, new bid not higher than last bid") + +// TODO naming +func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { + // Validate bid + if a.IsReversePhase() { + return a, sdk.ErrInternal("auction is not in forward phase") + } + if !a.Bid.IsLT(bid) { + return a, sdk.ErrInternal("auction in forward phase, new bid not higher than last bid") + } + if a.MaxBid.IsLT(bid) { + return a, sdk.ErrInternal("bid higher than max bid") + } + // Move Coins + // pay back previous bidder + if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { // catch edge case of someone updating their bid with a low balance, also don't send if amt is zero + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err } - if a.MaxBid.IsLT(bid) { - return a, sdk.ErrInternal("bid higher than max bid") - } - if lot.IsNegative() || a.Lot.IsLT(lot) { - return a, sdk.ErrInternal("lot out of bounds") - } - if lot.IsLT(a.Lot) && !bid.IsEqual(a.MaxBid) { - return a, sdk.ErrInternal("auction cannot enter reverse phase without bidding max bid") - } - } else { - // Auction is in reverse phase, it can never leave reverse phase - if !bid.IsEqual(a.MaxBid) { - return a, sdk.ErrInternal("") // not necessary - } - if lot.IsNegative() { - return a, sdk.ErrInternal("can't bid negative amount") - } - if !lot.IsLT(a.Lot) { - return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err } } + // pay increase in bid to auction initiator + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) + if err != nil { + return a, err + } + + // Update Auction + a.Bidder = bidder + a.Bid = bid + // increment timeout + a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) + + return a, nil +} + +func (k Keeper) PlaceBidForwardReverseReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { + // Validate bid + if !a.IsReversePhase() { + return a, sdk.ErrInternal("auction not in reverse phase") + } + if lot.IsNegative() { + return a, sdk.ErrInternal("can't bid negative amount") + } + if !lot.IsLT(a.Lot) { + return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") + } // Move Coins - bidIncrement := bid.Sub(a.Bid) - bidAmtToReturn := a.Bid - lotDecrement := a.Lot.Sub(lot) - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) - } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn.Add(bidIncrement))) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(bidIncrement)) - if err != nil { - return a, err + if !bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } } // FIXME paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids - lotPayouts, err := splitCoinIntoWeightedBuckets(lotDecrement, a.LotReturns.Weights) + lotPayouts, err := splitCoinIntoWeightedBuckets(a.Lot.Sub(lot), a.LotReturns.Weights) if err != nil { return a, err } @@ -219,12 +237,12 @@ func (k Keeper) PlaceBidForwardReverse(ctx sdk.Context, a types.ForwardReverseAu // Update Auction a.Bidder = bidder a.Lot = lot - a.Bid = bid // increment timeout a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) return a, nil } + func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { // Validate New Bid if lot.Denom != a.Lot.Denom { @@ -238,17 +256,15 @@ func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder } // Move Coins - bidAmtToReturn := a.Bid - if bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance - bidAmtToReturn = sdk.NewInt64Coin(a.Bid.Denom, 0) - } - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err - } - err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(bidAmtToReturn)) - if err != nil { - return a, err + if !bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid)) + if err != nil { + return a, err + } } // Update Auction diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 696c3382..1f10a28c 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -140,7 +140,8 @@ func TestForwardReverseAuctionBasic(t *testing.T) { } // Place a reverse bid - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // first bid up to max bid to switch phases + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // Check bidder's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) // Check seller's coins have increased diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 1aafaa36..fa5e1163 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -72,7 +72,7 @@ func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim // no ID Initiator: seller, Lot: lot, - Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided + Bidder: nil, Bid: sdk.NewInt64Coin(bidDenom, 0), EndTime: endTime, MaxEndTime: endTime, @@ -90,10 +90,9 @@ func (a ReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } // NewReverseAuction creates a new reverse auction func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) ReverseAuction { - // TODO setting the bidder here is a bit hacky - // Needs to be set so that when the first bid is placed, it is paid out to the initiator. - // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToModule. Not a problem currently but if checks/logic regarding modules accounts where added to those methods they would be bypassed. - // Alternative: set address to nil, and catch it in an if statement in place bid + // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) + // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. + // This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed. auction := ReverseAuction{BaseAuction{ // no ID Initiator: buyerModAccName, @@ -116,6 +115,10 @@ type ForwardReverseAuction struct { // WithID returns an auction with the ID set func (a ForwardReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } +func (a ForwardReverseAuction) IsReversePhase() bool { + return a.Bid.IsEqual(a.MaxBid) +} + func (a ForwardReverseAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s @@ -139,7 +142,7 @@ func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, ma // no ID Initiator: seller, Lot: lot, - Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided + Bidder: nil, Bid: sdk.NewInt64Coin(maxBid.Denom, 0), EndTime: EndTime, MaxEndTime: EndTime}, diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 1aa0276d..0bbf075d 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -12,8 +12,8 @@ import ( const ( // DefaultMaxAuctionDuration max length of auction DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour - // DefaultBidDuration how long an auction gets extended when someone bids, roughly 3 hours in blocks - DefaultBidDuration time.Duration = 3 * time.Hour + // DefaultBidDuration how long an auction gets extended when someone bids + DefaultBidDuration time.Duration = 1 * time.Hour ) // Parameter keys From 48a2d5b6dcc5a71f85b9cd69ed7f3d31324498b6 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 14:58:47 +0000 Subject: [PATCH 17/27] use only one coins field in MsgPlaceBid --- x/auction/abci_test.go | 2 +- x/auction/client/cli/tx.go | 15 +++++---------- x/auction/client/rest/tx.go | 11 ++--------- x/auction/handler.go | 2 +- x/auction/keeper/auctions.go | 23 +++++++++++------------ x/auction/keeper/auctions_test.go | 12 ++++++------ x/auction/types/msg.go | 20 +++++++------------- x/auction/types/msg_test.go | 24 +++++++++++++++++------- 8 files changed, 50 insertions(+), 59 deletions(-) diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 79f526b5..19bc1792 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -39,7 +39,7 @@ func TestKeeper_EndBlocker(t *testing.T) { auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) require.NoError(t, err) - require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30), c("token1", 20))) + require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30))) // Run the endblocker, simulating a block time 1ns before auction expiry preExpiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration - 1) diff --git a/x/auction/client/cli/tx.go b/x/auction/client/cli/tx.go index 2ced6610..e705eb74 100644 --- a/x/auction/client/cli/tx.go +++ b/x/auction/client/cli/tx.go @@ -33,9 +33,9 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { // GetCmdPlaceBid cli command for creating and modifying cdps. func GetCmdPlaceBid(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "placebid [AuctionID] [Bidder] [Bid] [Lot]", + Use: "placebid [auctionID] [amount]", Short: "place a bid on an auction", - Args: cobra.ExactArgs(4), + Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) @@ -46,18 +46,13 @@ func GetCmdPlaceBid(cdc *codec.Codec) *cobra.Command { return err } - bid, err := sdk.ParseCoin(args[2]) + amt, err := sdk.ParseCoin(args[2]) if err != nil { - fmt.Printf("invalid bid amount - %s \n", string(args[2])) + fmt.Printf("invalid amount - %s \n", string(args[2])) return err } - lot, err := sdk.ParseCoin(args[3]) - if err != nil { - fmt.Printf("invalid lot - %s \n", string(args[3])) - return err - } - msg := types.NewMsgPlaceBid(id, cliCtx.GetFromAddress(), bid, lot) + msg := types.NewMsgPlaceBid(id, cliCtx.GetFromAddress(), amt) err = msg.ValidateBasic() if err != nil { return err diff --git a/x/auction/client/rest/tx.go b/x/auction/client/rest/tx.go index 5773f24a..910714ca 100644 --- a/x/auction/client/rest/tx.go +++ b/x/auction/client/rest/tx.go @@ -33,7 +33,7 @@ const ( func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc( - fmt.Sprintf("/auction/bid/{%s}/{%s}/{%s}/{%s}", restAuctionID, restBidder, restBid, restLot), bidHandlerFn(cliCtx)).Methods("PUT") + fmt.Sprintf("/auction/bid/{%s}/{%s}/{%s}", restAuctionID, restBidder, restBid), bidHandlerFn(cliCtx)).Methods("PUT") } func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { @@ -44,7 +44,6 @@ func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { strAuctionID := vars[restAuctionID] bechBidder := vars[restBidder] strBid := vars[restBid] - strLot := vars[restLot] auctionID, err := strconv.ParseUint(strAuctionID, 10, 64) if err != nil { @@ -64,13 +63,7 @@ func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - lot, err := sdk.ParseCoin(strLot) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - msg := types.NewMsgPlaceBid(auctionID, bidder, bid, lot) + msg := types.NewMsgPlaceBid(auctionID, bidder, bid) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/auction/handler.go b/x/auction/handler.go index ec238957..f9b7c137 100644 --- a/x/auction/handler.go +++ b/x/auction/handler.go @@ -21,7 +21,7 @@ func NewHandler(keeper Keeper) sdk.Handler { func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Result { - err := keeper.PlaceBid(ctx, msg.AuctionID, msg.Bidder, msg.Bid, msg.Lot) + err := keeper.PlaceBid(ctx, msg.AuctionID, msg.Bidder, msg.Amount) if err != nil { return err.Result() } diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 38108079..6aa27fd6 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -68,8 +68,7 @@ func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot s } // PlaceBid places a bid on any auction. -// TODO passing bid and lot is weird when only one needed -func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) sdk.Error { +func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) sdk.Error { // get auction from store auction, found := k.GetAuction(ctx, auctionID) @@ -81,32 +80,26 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres if ctx.BlockTime().After(auction.GetEndTime()) { return sdk.ErrInternal("auction has closed") } - if auction.GetBid().Denom != bid.Denom { - return sdk.ErrInternal("bid has incorrect denom") - } - if auction.GetLot().Denom != lot.Denom { - return sdk.ErrInternal("lot has incorrect denom") - } // place bid var err sdk.Error var a types.Auction switch auc := auction.(type) { case types.ForwardAuction: - a, err = k.PlaceBidForward(ctx, auc, bidder, bid) + a, err = k.PlaceBidForward(ctx, auc, bidder, newAmount) if err != nil { return err } case types.ReverseAuction: - a, err = k.PlaceBidReverse(ctx, auc, bidder, lot) + a, err = k.PlaceBidReverse(ctx, auc, bidder, newAmount) if err != nil { return err } case types.ForwardReverseAuction: if !auc.IsReversePhase() { - a, err = k.PlaceBidForwardReverseForward(ctx, auc, bidder, bid) + a, err = k.PlaceBidForwardReverseForward(ctx, auc, bidder, newAmount) } else { - a, err = k.PlaceBidForwardReverseReverse(ctx, auc, bidder, lot) + a, err = k.PlaceBidForwardReverseReverse(ctx, auc, bidder, newAmount) } if err != nil { return err @@ -163,6 +156,9 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder // TODO naming func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { // Validate bid + if bid.Denom != a.Bid.Denom { + return a, sdk.ErrInternal("bid denom doesn't match auction") + } if a.IsReversePhase() { return a, sdk.ErrInternal("auction is not in forward phase") } @@ -201,6 +197,9 @@ func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardRe func (k Keeper) PlaceBidForwardReverseReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { // Validate bid + if lot.Denom != a.Lot.Denom { + return a, sdk.ErrInternal("lot denom doesn't match auction") + } if !a.IsReversePhase() { return a, sdk.ErrInternal("auction not in reverse phase") } diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 1f10a28c..7d7f3073 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -43,14 +43,14 @@ func TestForwardAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) // PlaceBid (bid: 10 token, lot: same as starting) - require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10), c("token1", 20))) // bid, lot + require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10))) // Check buyer's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) // Check seller's coins have not increased (because proceeds are burned) tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) // increment bid same bidder - err = keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 20), c("token1", 20)) + err = keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 20)) require.NoError(t, err) // Close auction at just at auction expiry time @@ -85,7 +85,7 @@ func TestReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyerAddr, nil) // zero coins // Place a bid - require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot + require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token2", 10))) // Check seller's coins have decreased tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) // Check buyer's coins have increased @@ -129,7 +129,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) // Place a forward bid - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10))) // Check bidder's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) // Check seller's coins have increased @@ -140,8 +140,8 @@ func TestForwardReverseAuctionBasic(t *testing.T) { } // Place a reverse bid - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // first bid up to max bid to switch phases - require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50))) // first bid up to max bid to switch phases + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token1", 15))) // Check bidder's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) // Check seller's coins have increased diff --git a/x/auction/types/msg.go b/x/auction/types/msg.go index 45c10472..dbaf43b2 100644 --- a/x/auction/types/msg.go +++ b/x/auction/types/msg.go @@ -9,17 +9,15 @@ var _ sdk.Msg = &MsgPlaceBid{} type MsgPlaceBid struct { AuctionID uint64 Bidder sdk.AccAddress // This can be a buyer (who increments bid), or a seller (who decrements lot) TODO rename to be clearer? - Bid sdk.Coin - Lot sdk.Coin + Amount sdk.Coin // The new bid or lot to set on the auction } // NewMsgPlaceBid returns a new MsgPlaceBid. -func NewMsgPlaceBid(auctionID uint64, bidder sdk.AccAddress, bid sdk.Coin, lot sdk.Coin) MsgPlaceBid { +func NewMsgPlaceBid(auctionID uint64, bidder sdk.AccAddress, amt sdk.Coin) MsgPlaceBid { return MsgPlaceBid{ AuctionID: auctionID, Bidder: bidder, - Bid: bid, - Lot: lot, + Amount: amt, } } @@ -29,18 +27,14 @@ func (msg MsgPlaceBid) Route() string { return RouterKey } // Type returns a human-readable string for the message, intended for utilization within tags. func (msg MsgPlaceBid) Type() string { return "place_bid" } -// ValidateBasic does a simple validation check that doesn't require access to any other information. +// ValidateBasic does a simple validation check that doesn't require access to state. func (msg MsgPlaceBid) ValidateBasic() sdk.Error { if msg.Bidder.Empty() { return sdk.ErrInternal("invalid (empty) bidder address") } - if msg.Bid.Amount.LT(sdk.ZeroInt()) { - return sdk.ErrInternal("invalid (negative) bid amount") + if !msg.Amount.IsValid() { + return sdk.ErrInternal("invalid bid amount") } - if msg.Lot.Amount.LT(sdk.ZeroInt()) { - return sdk.ErrInternal("invalid (negative) lot amount") - } - // TODO check coin denoms return nil } @@ -53,4 +47,4 @@ func (msg MsgPlaceBid) GetSignBytes() []byte { // GetSigners returns the addresses of signers that must sign. func (msg MsgPlaceBid) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Bidder} -} \ No newline at end of file +} diff --git a/x/auction/types/msg_test.go b/x/auction/types/msg_test.go index cfb51b5c..f78e7102 100644 --- a/x/auction/types/msg_test.go +++ b/x/auction/types/msg_test.go @@ -14,19 +14,29 @@ func TestMsgPlaceBid_ValidateBasic(t *testing.T) { msg MsgPlaceBid expectPass bool }{ - {"normal", MsgPlaceBid{0, addr, sdk.NewInt64Coin("usdx", 10), sdk.NewInt64Coin("kava", 20)}, true}, - {"emptyAddr", MsgPlaceBid{0, sdk.AccAddress{}, sdk.NewInt64Coin("usdx", 10), sdk.NewInt64Coin("kava", 20)}, false}, - {"negativeBid", MsgPlaceBid{0, addr, sdk.Coin{"usdx", sdk.NewInt(-10)}, sdk.NewInt64Coin("kava", 20)}, false}, - {"negativeLot", MsgPlaceBid{0, addr, sdk.NewInt64Coin("usdx", 10), sdk.Coin{"kava", sdk.NewInt(-20)}}, false}, - {"zerocoins", MsgPlaceBid{0, addr, sdk.NewInt64Coin("usdx", 0), sdk.NewInt64Coin("kava", 0)}, true}, + {"normal", + NewMsgPlaceBid(0, addr, c("token", 10)), + true}, + {"emptyAddr", + NewMsgPlaceBid(0, sdk.AccAddress{}, c("token", 10)), + false}, + {"negativeAmount", + NewMsgPlaceBid(0, addr, sdk.Coin{Denom: "token", Amount: sdk.NewInt(-10)}), + false}, + {"zeroAmount", + NewMsgPlaceBid(0, addr, c("token", 0)), + true}, } + for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if tc.expectPass { - require.Nil(t, tc.msg.ValidateBasic()) + require.NoError(t, tc.msg.ValidateBasic()) } else { - require.NotNil(t, tc.msg.ValidateBasic()) + require.Error(t, tc.msg.ValidateBasic()) } }) } } + +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } From 08689305cc53a72598db0775db249b1905045b95 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 15:43:42 +0000 Subject: [PATCH 18/27] remove uncessary Auction interface methods --- x/auction/keeper/auctions.go | 52 +++++++++++++++++++++--------------- x/auction/types/auctions.go | 12 --------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 6aa27fd6..51e05a43 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -83,23 +83,21 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres // place bid var err sdk.Error - var a types.Auction - switch auc := auction.(type) { + var updatedAuction types.Auction + switch a := auction.(type) { case types.ForwardAuction: - a, err = k.PlaceBidForward(ctx, auc, bidder, newAmount) - if err != nil { + if updatedAuction, err = k.PlaceBidForward(ctx, a, bidder, newAmount); err != nil { return err } case types.ReverseAuction: - a, err = k.PlaceBidReverse(ctx, auc, bidder, newAmount) - if err != nil { + if updatedAuction, err = k.PlaceBidReverse(ctx, a, bidder, newAmount); err != nil { return err } case types.ForwardReverseAuction: - if !auc.IsReversePhase() { - a, err = k.PlaceBidForwardReverseForward(ctx, auc, bidder, newAmount) + if !a.IsReversePhase() { + updatedAuction, err = k.PlaceBidForwardReverseForward(ctx, a, bidder, newAmount) } else { - a, err = k.PlaceBidForwardReverseReverse(ctx, auc, bidder, newAmount) + updatedAuction, err = k.PlaceBidForwardReverseReverse(ctx, a, bidder, newAmount) } if err != nil { return err @@ -109,12 +107,12 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres } // store updated auction - k.SetAuction(ctx, a) + k.SetAuction(ctx, updatedAuction) return nil } func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) { - // Valid New Bid + // Validate New Bid if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") } @@ -155,7 +153,7 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder // TODO naming func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { - // Validate bid + // Validate new bid if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") } @@ -289,27 +287,28 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { } // payout to the last bidder - var err sdk.Error switch auc := auction.(type) { - case types.ForwardAuction, types.ForwardReverseAuction: - err = k.PayoutAuctionLot(ctx, auc) - if err != nil { + case types.ForwardAuction: + if err := k.PayoutForwardAuction(ctx, auc); err != nil { return err } case types.ReverseAuction: - err = k.MintAndPayoutAuctionLot(ctx, auc) - if err != nil { + if err := k.PayoutReverseAuction(ctx, auc); err != nil { + return err + } + case types.ForwardReverseAuction: + if err := k.PayoutForwardReverseAuction(ctx, auc); err != nil { return err } default: panic("unrecognized auction type") } - // Delete auction from store (and queue) k.DeleteAuction(ctx, auctionID) return nil } -func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) sdk.Error { + +func (k Keeper) PayoutReverseAuction(ctx sdk.Context, a types.ReverseAuction) sdk.Error { err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -320,8 +319,17 @@ func (k Keeper) MintAndPayoutAuctionLot(ctx sdk.Context, a types.ReverseAuction) } return nil } -func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error { - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.GetBidder(), sdk.NewCoins(a.GetLot())) + +func (k Keeper) PayoutForwardAuction(ctx sdk.Context, a types.ForwardAuction) sdk.Error { + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) + if err != nil { + return err + } + return nil +} + +func (k Keeper) PayoutForwardReverseAuction(ctx sdk.Context, a types.ForwardReverseAuction) sdk.Error { + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { return err } diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index fa5e1163..53857084 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -12,9 +12,6 @@ import ( type Auction interface { GetID() uint64 WithID(uint64) Auction - GetBidder() sdk.AccAddress - GetBid() sdk.Coin - GetLot() sdk.Coin GetEndTime() time.Time } @@ -32,15 +29,6 @@ type BaseAuction struct { // GetID getter for auction ID func (a BaseAuction) GetID() uint64 { return a.ID } -// GetBid getter for auction bid -func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } - -// GetBid getter for auction lot -func (a BaseAuction) GetBid() sdk.Coin { return a.Bid } - -// GetLot getter for auction lot -func (a BaseAuction) GetLot() sdk.Coin { return a.Lot } - // GetEndTime getter for auction end time func (a BaseAuction) GetEndTime() time.Time { return a.EndTime } From c239932297b748d3f7fa73facf6cd1b9dc0b8745 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 16:09:19 +0000 Subject: [PATCH 19/27] give auction types more accurate names --- x/auction/abci_test.go | 2 +- x/auction/alias.go | 62 +++++++++++++++---------------- x/auction/keeper/auctions.go | 59 +++++++++++++++-------------- x/auction/keeper/auctions_test.go | 18 ++++----- x/auction/keeper/keeper_test.go | 8 ++-- x/auction/spec/01_concepts.md | 6 +-- x/auction/spec/02_state.md | 12 +++--- x/auction/spec/03_messages.md | 6 +-- x/auction/types/auctions.go | 40 ++++++++++---------- x/auction/types/auctions_test.go | 40 ++++++++++---------- x/auction/types/codec.go | 6 +-- 11 files changed, 129 insertions(+), 130 deletions(-) diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 19bc1792..418ca49a 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -37,7 +37,7 @@ func TestKeeper_EndBlocker(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) keeper := tApp.GetAuctionKeeper() - auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) + auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) require.NoError(t, err) require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30))) diff --git a/x/auction/alias.go b/x/auction/alias.go index ec4ad50f..99b63c4f 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -22,24 +22,24 @@ const ( var ( // functions aliases - NewForwardAuction = types.NewForwardAuction - NewReverseAuction = types.NewReverseAuction - NewForwardReverseAuction = types.NewForwardReverseAuction - NewWeightedAddresses = types.NewWeightedAddresses - RegisterCodec = types.RegisterCodec - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - ValidateGenesis = types.ValidateGenesis - GetAuctionKey = types.GetAuctionKey - GetAuctionByTimeKey = types.GetAuctionByTimeKey - Uint64FromBytes = types.Uint64FromBytes - Uint64ToBytes = types.Uint64ToBytes - NewMsgPlaceBid = types.NewMsgPlaceBid - NewParams = types.NewParams - DefaultParams = types.DefaultParams - ParamKeyTable = types.ParamKeyTable - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier + NewSurplusAuction = types.NewSurplusAuction + NewDebtAuction = types.NewDebtAuction + NewCollateralAuction = types.NewCollateralAuction + NewWeightedAddresses = types.NewWeightedAddresses + RegisterCodec = types.RegisterCodec + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + GetAuctionKey = types.GetAuctionKey + GetAuctionByTimeKey = types.GetAuctionByTimeKey + Uint64FromBytes = types.Uint64FromBytes + Uint64ToBytes = types.Uint64ToBytes + NewMsgPlaceBid = types.NewMsgPlaceBid + NewParams = types.NewParams + DefaultParams = types.DefaultParams + ParamKeyTable = types.ParamKeyTable + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier // variable aliases ModuleCdc = types.ModuleCdc @@ -51,17 +51,17 @@ var ( ) type ( - Auction = types.Auction - BaseAuction = types.BaseAuction - ForwardAuction = types.ForwardAuction - ReverseAuction = types.ReverseAuction - ForwardReverseAuction = types.ForwardReverseAuction - WeightedAddresses = types.WeightedAddresses - SupplyKeeper = types.SupplyKeeper - Auctions = types.Auctions - GenesisState = types.GenesisState - MsgPlaceBid = types.MsgPlaceBid - Params = types.Params - QueryResAuctions = types.QueryResAuctions - Keeper = keeper.Keeper + Auction = types.Auction + BaseAuction = types.BaseAuction + SurplusAuction = types.SurplusAuction + DebtAuction = types.DebtAuction + CollateralAuction = types.CollateralAuction + WeightedAddresses = types.WeightedAddresses + SupplyKeeper = types.SupplyKeeper + Auctions = types.Auctions + GenesisState = types.GenesisState + MsgPlaceBid = types.MsgPlaceBid + Params = types.Params + QueryResAuctions = types.QueryResAuctions + Keeper = keeper.Keeper ) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 51e05a43..debac160 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -9,10 +9,10 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -// StartForwardAuction starts a normal auction that mints the sold coins. -func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { +// StartSurplusAuction starts a normal auction that mints the sold coins. +func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { // create auction - auction := types.NewForwardAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) + auction := types.NewSurplusAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) // take coins from module account err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) @@ -27,10 +27,10 @@ func (k Keeper) StartForwardAuction(ctx sdk.Context, seller string, lot sdk.Coin return auctionID, nil } -// StartReverseAuction starts an auction where sellers compete by offering decreasing prices. -func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { +// StartDebtAuction starts an auction where sellers compete by offering decreasing prices. +func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { // create auction - auction := types.NewReverseAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) + auction := types.NewDebtAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) @@ -45,14 +45,14 @@ func (k Keeper) StartReverseAuction(ctx sdk.Context, buyer string, bid sdk.Coin, return auctionID, nil } -// StartForwardReverseAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. -func (k Keeper) StartForwardReverseAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { +// StartCollateralAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. +func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { // create auction weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) if err != nil { return 0, err } - auction := types.NewForwardReverseAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) + auction := types.NewCollateralAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) // take coins from module account err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) @@ -85,19 +85,19 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres var err sdk.Error var updatedAuction types.Auction switch a := auction.(type) { - case types.ForwardAuction: - if updatedAuction, err = k.PlaceBidForward(ctx, a, bidder, newAmount); err != nil { + case types.SurplusAuction: + if updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount); err != nil { return err } - case types.ReverseAuction: - if updatedAuction, err = k.PlaceBidReverse(ctx, a, bidder, newAmount); err != nil { + case types.DebtAuction: + if updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount); err != nil { return err } - case types.ForwardReverseAuction: + case types.CollateralAuction: if !a.IsReversePhase() { - updatedAuction, err = k.PlaceBidForwardReverseForward(ctx, a, bidder, newAmount) + updatedAuction, err = k.PlaceForwardBidCollateral(ctx, a, bidder, newAmount) } else { - updatedAuction, err = k.PlaceBidForwardReverseReverse(ctx, a, bidder, newAmount) + updatedAuction, err = k.PlaceReverseBidCollateral(ctx, a, bidder, newAmount) } if err != nil { return err @@ -111,7 +111,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres return nil } -func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) { +func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, sdk.Error) { // Validate New Bid if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") @@ -151,8 +151,7 @@ func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder return a, nil } -// TODO naming -func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { +func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, sdk.Error) { // Validate new bid if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") @@ -193,7 +192,7 @@ func (k Keeper) PlaceBidForwardReverseForward(ctx sdk.Context, a types.ForwardRe return a, nil } -func (k Keeper) PlaceBidForwardReverseReverse(ctx sdk.Context, a types.ForwardReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ForwardReverseAuction, sdk.Error) { +func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, sdk.Error) { // Validate bid if lot.Denom != a.Lot.Denom { return a, sdk.ErrInternal("lot denom doesn't match auction") @@ -240,7 +239,7 @@ func (k Keeper) PlaceBidForwardReverseReverse(ctx sdk.Context, a types.ForwardRe return a, nil } -func (k Keeper) PlaceBidReverse(ctx sdk.Context, a types.ReverseAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.ReverseAuction, sdk.Error) { +func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, sdk.Error) { // Validate New Bid if lot.Denom != a.Lot.Denom { return a, sdk.ErrInternal("lot denom doesn't match auction") @@ -288,16 +287,16 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { // payout to the last bidder switch auc := auction.(type) { - case types.ForwardAuction: - if err := k.PayoutForwardAuction(ctx, auc); err != nil { + case types.SurplusAuction: + if err := k.PayoutSurplusAuction(ctx, auc); err != nil { return err } - case types.ReverseAuction: - if err := k.PayoutReverseAuction(ctx, auc); err != nil { + case types.DebtAuction: + if err := k.PayoutDebtAuction(ctx, auc); err != nil { return err } - case types.ForwardReverseAuction: - if err := k.PayoutForwardReverseAuction(ctx, auc); err != nil { + case types.CollateralAuction: + if err := k.PayoutCollateralAuction(ctx, auc); err != nil { return err } default: @@ -308,7 +307,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { return nil } -func (k Keeper) PayoutReverseAuction(ctx sdk.Context, a types.ReverseAuction) sdk.Error { +func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Error { err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -320,7 +319,7 @@ func (k Keeper) PayoutReverseAuction(ctx sdk.Context, a types.ReverseAuction) sd return nil } -func (k Keeper) PayoutForwardAuction(ctx sdk.Context, a types.ForwardAuction) sdk.Error { +func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sdk.Error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -328,7 +327,7 @@ func (k Keeper) PayoutForwardAuction(ctx sdk.Context, a types.ForwardAuction) sd return nil } -func (k Keeper) PayoutForwardReverseAuction(ctx sdk.Context, a types.ForwardReverseAuction) sdk.Error { +func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) sdk.Error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { return err diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 7d7f3073..6e4ffeb4 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -16,7 +16,7 @@ import ( "github.com/kava-labs/kava/x/liquidator" ) -func TestForwardAuctionBasic(t *testing.T) { +func TestSurplusAuctionBasic(t *testing.T) { // Setup _, addrs := app.GeneratePrivKeyAddressPairs(1) buyer := addrs[0] @@ -37,7 +37,7 @@ func TestForwardAuctionBasic(t *testing.T) { keeper := tApp.GetAuctionKeeper() // Create an auction (lot: 20 token1, initialBid: 0 token2) - auctionID, err := keeper.StartForwardAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom + auctionID, err := keeper.StartSurplusAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom require.NoError(t, err) // Check seller's coins have decreased tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) @@ -60,7 +60,7 @@ func TestForwardAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 80))) } -func TestReverseAuctionBasic(t *testing.T) { +func TestDebtAuctionBasic(t *testing.T) { // Setup _, addrs := app.GeneratePrivKeyAddressPairs(1) seller := addrs[0] @@ -79,7 +79,7 @@ func TestReverseAuctionBasic(t *testing.T) { keeper := tApp.GetAuctionKeeper() // Start auction - auctionID, err := keeper.StartReverseAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999)) // buyer, bid, initialLot + auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999)) // buyer, bid, initialLot require.NoError(t, err) // Check buyer's coins have not decreased, as lot is minted at the end tApp.CheckBalance(t, ctx, buyerAddr, nil) // zero coins @@ -98,7 +98,7 @@ func TestReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) } -func TestForwardReverseAuctionBasic(t *testing.T) { +func TestCollateralAuctionBasic(t *testing.T) { // Setup _, addrs := app.GeneratePrivKeyAddressPairs(4) buyer := addrs[0] @@ -123,7 +123,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { keeper := tApp.GetAuctionKeeper() // Start auction - auctionID, err := keeper.StartForwardReverseAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) // seller, lot, maxBid, otherPerson + auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) // seller, lot, maxBid, otherPerson require.NoError(t, err) // Check seller's coins have decreased tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) @@ -158,7 +158,7 @@ func TestForwardReverseAuctionBasic(t *testing.T) { tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) } -func TestStartForwardAuction(t *testing.T) { +func TestStartSurplusAuction(t *testing.T) { someTime := time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC) type args struct { seller string @@ -211,7 +211,7 @@ func TestStartForwardAuction(t *testing.T) { keeper := tApp.GetAuctionKeeper() // run function under test - id, err := keeper.StartForwardAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) + id, err := keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) // check sk := tApp.GetSupplyKeeper() @@ -224,7 +224,7 @@ func TestStartForwardAuction(t *testing.T) { require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins) // check auction in store and is correct require.True(t, found) - expectedAuction := types.Auction(types.ForwardAuction{BaseAuction: types.BaseAuction{ + expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{ ID: 0, Initiator: tc.args.seller, Lot: tc.args.lot, diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index cc6fbce1..3c59d9ae 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -18,7 +18,7 @@ func SetGetDeleteAuction(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal var id uint64 = 5 - auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime).WithID(id) + auction := types.NewSurplusAuction("some_module", c("usdx", 100), "kava", someTime).WithID(id) // write and read from store keeper.SetAuction(ctx, auction) @@ -73,9 +73,9 @@ func TestIterateAuctions(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) auctions := []types.Auction{ - types.NewForwardAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0), - types.NewReverseAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(1), - types.NewForwardReverseAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}).WithID(2), + types.NewSurplusAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0), + types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(1), + types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}).WithID(2), } for _, a := range auctions { keeper.SetAuction(ctx, a) diff --git a/x/auction/spec/01_concepts.md b/x/auction/spec/01_concepts.md index f656b615..5a4196ac 100644 --- a/x/auction/spec/01_concepts.md +++ b/x/auction/spec/01_concepts.md @@ -6,8 +6,8 @@ order: 1 Auctions are broken down into three distinct types, which correspond to three specific functionalities within the CDP system. -* **Forward Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a forward auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, forward auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX. -* **Reverse Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, reverse auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA. -* **Forward Reverse Auction:** An two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example. forward reverse auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM. +* **Surplus Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a forward auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, forward auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX. +* **Debt Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, reverse auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA. +* **Surplus Reverse Auction:** An two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example. forward reverse auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM. Auctions are always initiated by another module, and not directly by users. Auctions start with an expiry, the time at which the auction is guaranteed to end, even if there have been no bidders. After each bid, the auction is extended by a specific amount of time, `BidDuration`. In the case that increasing the auction time by `BidDuration` would cause the auction to go past its expiry, the expiry is chosen as the ending time. diff --git a/x/auction/spec/02_state.md b/x/auction/spec/02_state.md index a1c39f84..e24f1c02 100644 --- a/x/auction/spec/02_state.md +++ b/x/auction/spec/02_state.md @@ -51,13 +51,13 @@ type BaseAuction struct { MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } -// ForwardAuction type for forward auctions -type ForwardAuction struct { +//SurplusAuction type for forward auctions +typeSurplusAuction struct { BaseAuction } -// ReverseAuction type for reverse auctions -type ReverseAuction struct { +// DebtAuction type for reverse auctions +type DebtAuction struct { BaseAuction } @@ -67,8 +67,8 @@ type WeightedAddresses struct { Weights []sdk.Int } -// ForwardReverseAuction type for forward reverse auction -type ForwardReverseAuction struct { +// CollateralAuction type for forward reverse auction +type CollateralAuction struct { BaseAuction MaxBid sdk.Coin LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. diff --git a/x/auction/spec/03_messages.md b/x/auction/spec/03_messages.md index 5c8ece91..6b59d9c2 100644 --- a/x/auction/spec/03_messages.md +++ b/x/auction/spec/03_messages.md @@ -21,14 +21,14 @@ type MsgPlaceBid struct { **State Modifications:** * Update bidder if different than previous bidder -* For forward auctions: +* For Surplus auctions: * Update Bid Amount * Return bid coins to previous bidder * Burn coins equal to the increment in the bid (CurrentBid - PreviousBid) -* For Reverse auctions: +* For Debt auctions: * Update lot amount * Return bid coins to previous bidder -* For Forward Reverse auctions: +* For Collateral auctions: * Return bid coins to previous bidder * If in forward phase: * Update bid amount diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 53857084..ec685006 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -46,17 +46,17 @@ func (a BaseAuction) String() string { ) } -// ForwardAuction type for forward auctions -type ForwardAuction struct { +// SurplusAuction type for forward auctions +type SurplusAuction struct { BaseAuction } // WithID returns an auction with the ID set -func (a ForwardAuction) WithID(id uint64) Auction { a.ID = id; return a } +func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a } -// NewForwardAuction creates a new forward auction -func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) ForwardAuction { - auction := ForwardAuction{BaseAuction{ +// NewSurplusAuction creates a new forward auction +func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction { + auction := SurplusAuction{BaseAuction{ // no ID Initiator: seller, Lot: lot, @@ -68,20 +68,20 @@ func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim return auction } -// ReverseAuction type for reverse auctions -type ReverseAuction struct { +// DebtAuction type for reverse auctions +type DebtAuction struct { BaseAuction } // WithID returns an auction with the ID set -func (a ReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } +func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a } -// NewReverseAuction creates a new reverse auction -func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) ReverseAuction { +// NewDebtAuction creates a new reverse auction +func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) DebtAuction { // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. // This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed. - auction := ReverseAuction{BaseAuction{ + auction := DebtAuction{BaseAuction{ // no ID Initiator: buyerModAccName, Lot: initialLot, @@ -93,21 +93,21 @@ func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin return auction } -// ForwardReverseAuction type for forward reverse auction -type ForwardReverseAuction struct { +// CollateralAuction type for forward reverse auction +type CollateralAuction struct { BaseAuction MaxBid sdk.Coin LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. } // WithID returns an auction with the ID set -func (a ForwardReverseAuction) WithID(id uint64) Auction { a.ID = id; return a } +func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a } -func (a ForwardReverseAuction) IsReversePhase() bool { +func (a CollateralAuction) IsReversePhase() bool { return a.Bid.IsEqual(a.MaxBid) } -func (a ForwardReverseAuction) String() string { +func (a CollateralAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s Lot: %s @@ -123,9 +123,9 @@ func (a ForwardReverseAuction) String() string { ) } -// NewForwardReverseAuction creates a new forward reverse auction -func NewForwardReverseAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) ForwardReverseAuction { - auction := ForwardReverseAuction{ +// NewCollateralAuction creates a new forward reverse auction +func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) CollateralAuction { + auction := CollateralAuction{ BaseAuction: BaseAuction{ // no ID Initiator: seller, diff --git a/x/auction/types/auctions_test.go b/x/auction/types/auctions_test.go index bd0db597..a4d059d6 100644 --- a/x/auction/types/auctions_test.go +++ b/x/auction/types/auctions_test.go @@ -2,7 +2,7 @@ package types // // TODO can this be less verbose? Should PlaceBid() be split into smaller functions? // // It would be possible to combine all auction tests into one test runner. -// func TestForwardAuction_PlaceBid(t *testing.T) { +// func TesSurplusAuction_PlaceBid(t *testing.T) { // seller := sdk.AccAddress([]byte("a_seller")) // buyer1 := sdk.AccAddress([]byte("buyer1")) // buyer2 := sdk.AccAddress([]byte("buyer2")) @@ -17,7 +17,7 @@ package types // } // tests := []struct { // name string -// auction ForwardAuction +// auction SurplusAuction // args args // expectedOutputs []BankOutput // expectedInputs []BankInput @@ -28,7 +28,7 @@ package types // }{ // { // "normal", -// ForwardAuction{BaseAuction{ +// SurplusAuction{BaseAuction{ // Initiator: seller, // Lot: c("usdx", 100), // Bidder: buyer1, @@ -46,7 +46,7 @@ package types // }, // { // "lowBid", -// ForwardAuction{BaseAuction{ +// SurplusAuction{BaseAuction{ // Initiator: seller, // Lot: c("usdx", 100), // Bidder: buyer1, @@ -64,7 +64,7 @@ package types // }, // { // "equalBid", -// ForwardAuction{BaseAuction{ +// SurplusAuction{BaseAuction{ // Initiator: seller, // Lot: c("usdx", 100), // Bidder: buyer1, @@ -82,7 +82,7 @@ package types // }, // { // "timeout", -// ForwardAuction{BaseAuction{ +// SurplusAuction{BaseAuction{ // Initiator: seller, // Lot: c("usdx", 100), // Bidder: buyer1, @@ -100,7 +100,7 @@ package types // }, // { // "hitMaxEndTime", -// ForwardAuction{BaseAuction{ +// SurplusAuction{BaseAuction{ // Initiator: seller, // Lot: c("usdx", 100), // Bidder: buyer1, @@ -139,7 +139,7 @@ package types // } // } -// func TestReverseAuction_PlaceBid(t *testing.T) { +// func TestDebtAuction_PlaceBid(t *testing.T) { // buyer := sdk.AccAddress([]byte("a_buyer")) // seller1 := sdk.AccAddress([]byte("seller1")) // seller2 := sdk.AccAddress([]byte("seller2")) @@ -154,7 +154,7 @@ package types // } // tests := []struct { // name string -// auction ReverseAuction +// auction DebtAuction // args args // expectedOutputs []BankOutput // expectedInputs []BankInput @@ -165,7 +165,7 @@ package types // }{ // { // "normal", -// ReverseAuction{BaseAuction{ +// DebtAuction{BaseAuction{ // Initiator: buyer, // Lot: c("kava", 10), // Bidder: seller1, @@ -183,7 +183,7 @@ package types // }, // { // "highBid", -// ReverseAuction{BaseAuction{ +// DebtAuction{BaseAuction{ // Initiator: buyer, // Lot: c("kava", 10), // Bidder: seller1, @@ -201,7 +201,7 @@ package types // }, // { // "equalBid", -// ReverseAuction{BaseAuction{ +// DebtAuction{BaseAuction{ // Initiator: buyer, // Lot: c("kava", 10), // Bidder: seller1, @@ -219,7 +219,7 @@ package types // }, // { // "timeout", -// ReverseAuction{BaseAuction{ +// DebtAuction{BaseAuction{ // Initiator: buyer, // Lot: c("kava", 10), // Bidder: seller1, @@ -237,7 +237,7 @@ package types // }, // { // "hitMaxEndTime", -// ReverseAuction{BaseAuction{ +// DebtAuction{BaseAuction{ // Initiator: buyer, // Lot: c("kava", 10), // Bidder: seller1, @@ -276,7 +276,7 @@ package types // } // } -// func TestForwardReverseAuction_PlaceBid(t *testing.T) { +// func TestCollateralAuction_PlaceBid(t *testing.T) { // cdpOwner := sdk.AccAddress([]byte("a_cdp_owner")) // seller := sdk.AccAddress([]byte("a_seller")) // buyer1 := sdk.AccAddress([]byte("buyer1")) @@ -292,7 +292,7 @@ package types // } // tests := []struct { // name string -// auction ForwardReverseAuction +// auction CollateralAuction // args args // expectedOutputs []BankOutput // expectedInputs []BankInput @@ -304,7 +304,7 @@ package types // }{ // { // "normalForwardBid", -// ForwardReverseAuction{BaseAuction: BaseAuction{ +// CollateralAuction{BaseAuction: BaseAuction{ // Initiator: seller, // Lot: c("xrp", 100), // Bidder: buyer1, @@ -325,7 +325,7 @@ package types // }, // { // "normalSwitchOverBid", -// ForwardReverseAuction{BaseAuction: BaseAuction{ +// CollateralAuction{BaseAuction: BaseAuction{ // Initiator: seller, // Lot: c("xrp", 100), // Bidder: buyer1, @@ -345,8 +345,8 @@ package types // true, // }, // { -// "normalReverseBid", -// ForwardReverseAuction{BaseAuction: BaseAuction{ +// "normalDebtBid", +// CollateralAuction{BaseAuction: BaseAuction{ // Initiator: seller, // Lot: c("xrp", 99), // Bidder: buyer1, diff --git a/x/auction/types/codec.go b/x/auction/types/codec.go index 75ede175..12f8f9eb 100644 --- a/x/auction/types/codec.go +++ b/x/auction/types/codec.go @@ -17,7 +17,7 @@ func RegisterCodec(cdc *codec.Codec) { // Register the Auction interface and concrete types cdc.RegisterInterface((*Auction)(nil), nil) - cdc.RegisterConcrete(ForwardAuction{}, "auction/ForwardAuction", nil) - cdc.RegisterConcrete(ReverseAuction{}, "auction/ReverseAuction", nil) - cdc.RegisterConcrete(ForwardReverseAuction{}, "auction/ForwardReverseAuction", nil) + cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil) + cdc.RegisterConcrete(DebtAuction{}, "auction/DebtAuction", nil) + cdc.RegisterConcrete(CollateralAuction{}, "auction/CollateralAuction", nil) } From 08d6bc2284503ad7bbda90a63b79321d4b6999d9 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 16:14:55 +0000 Subject: [PATCH 20/27] remove vuepress comments from spec --- x/auction/spec/01_concepts.md | 4 ---- x/auction/spec/02_state.md | 4 ---- x/auction/spec/03_messages.md | 4 ---- x/auction/spec/04_events.md | 4 ---- x/auction/spec/05_params.md | 4 ---- x/auction/spec/{06_begin_block.md => 06_end_block.md} | 6 ++---- x/auction/spec/README.md | 7 ------- 7 files changed, 2 insertions(+), 31 deletions(-) rename x/auction/spec/{06_begin_block.md => 06_end_block.md} (67%) diff --git a/x/auction/spec/01_concepts.md b/x/auction/spec/01_concepts.md index 5a4196ac..08943870 100644 --- a/x/auction/spec/01_concepts.md +++ b/x/auction/spec/01_concepts.md @@ -1,7 +1,3 @@ - - # Concepts Auctions are broken down into three distinct types, which correspond to three specific functionalities within the CDP system. diff --git a/x/auction/spec/02_state.md b/x/auction/spec/02_state.md index e24f1c02..fe383ed6 100644 --- a/x/auction/spec/02_state.md +++ b/x/auction/spec/02_state.md @@ -1,7 +1,3 @@ - - # State ## Parameters and genesis state diff --git a/x/auction/spec/03_messages.md b/x/auction/spec/03_messages.md index 6b59d9c2..b97d9745 100644 --- a/x/auction/spec/03_messages.md +++ b/x/auction/spec/03_messages.md @@ -1,7 +1,3 @@ - - # Messages ## Bidding diff --git a/x/auction/spec/04_events.md b/x/auction/spec/04_events.md index 3bc15051..0c6b3238 100644 --- a/x/auction/spec/04_events.md +++ b/x/auction/spec/04_events.md @@ -1,7 +1,3 @@ - - # Events - # Parameters The auction module contains the following parameters: diff --git a/x/auction/spec/06_begin_block.md b/x/auction/spec/06_end_block.md similarity index 67% rename from x/auction/spec/06_begin_block.md rename to x/auction/spec/06_end_block.md index 2ecfcd54..43694df9 100644 --- a/x/auction/spec/06_begin_block.md +++ b/x/auction/spec/06_end_block.md @@ -1,8 +1,6 @@ - +# End Block -At the beginning of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows: +At the end of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows: ```go var expiredAuctions []uint64 diff --git a/x/auction/spec/README.md b/x/auction/spec/README.md index c68ab587..32ae71d1 100644 --- a/x/auction/spec/README.md +++ b/x/auction/spec/README.md @@ -1,10 +1,3 @@ - - # `auction` From fecfee5077cd4032a2b8cdd485265c559f526d92 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 16:21:42 +0000 Subject: [PATCH 21/27] minor spec updates --- x/auction/spec/01_concepts.md | 6 +++--- x/auction/spec/02_state.md | 7 ++----- x/auction/spec/03_messages.md | 13 ++++++------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/x/auction/spec/01_concepts.md b/x/auction/spec/01_concepts.md index 08943870..a7365d0c 100644 --- a/x/auction/spec/01_concepts.md +++ b/x/auction/spec/01_concepts.md @@ -2,8 +2,8 @@ Auctions are broken down into three distinct types, which correspond to three specific functionalities within the CDP system. -* **Surplus Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a forward auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, forward auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX. -* **Debt Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, reverse auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA. -* **Surplus Reverse Auction:** An two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example. forward reverse auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM. +* **Surplus Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a surplus auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, surplus auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX. +* **Debt Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, debt auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA. +* **Surplus Reverse Auction:** Are two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example, collateral auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM. Auctions are always initiated by another module, and not directly by users. Auctions start with an expiry, the time at which the auction is guaranteed to end, even if there have been no bidders. After each bid, the auction is extended by a specific amount of time, `BidDuration`. In the case that increasing the auction time by `BidDuration` would cause the auction to go past its expiry, the expiry is chosen as the ending time. diff --git a/x/auction/spec/02_state.md b/x/auction/spec/02_state.md index fe383ed6..075cf08c 100644 --- a/x/auction/spec/02_state.md +++ b/x/auction/spec/02_state.md @@ -30,9 +30,6 @@ type GenesisState struct { type Auction interface { GetID() uint64 WithID(uint64) Auction - GetBidder() sdk.AccAddress - GetBid() sdk.Coin - GetLot() sdk.Coin GetEndTime() time.Time } @@ -47,8 +44,8 @@ type BaseAuction struct { MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } -//SurplusAuction type for forward auctions -typeSurplusAuction struct { +// SurplusAuction type for forward auctions +type SurplusAuction struct { BaseAuction } diff --git a/x/auction/spec/03_messages.md b/x/auction/spec/03_messages.md index b97d9745..3a26a722 100644 --- a/x/auction/spec/03_messages.md +++ b/x/auction/spec/03_messages.md @@ -9,8 +9,7 @@ Users can bid on auctions using the `MsgPlaceBid` message type. All auction type type MsgPlaceBid struct { AuctionID uint64 Bidder sdk.AccAddress - Bid sdk.Coin - Lot sdk.Coin + Amount sdk.Coin } ``` @@ -18,16 +17,16 @@ type MsgPlaceBid struct { * Update bidder if different than previous bidder * For Surplus auctions: - * Update Bid Amount + * Update Bid to msg.Amount * Return bid coins to previous bidder * Burn coins equal to the increment in the bid (CurrentBid - PreviousBid) * For Debt auctions: - * Update lot amount + * Update Lot amount to msg.Amount * Return bid coins to previous bidder * For Collateral auctions: * Return bid coins to previous bidder * If in forward phase: - * Update bid amount + * Update Bid amount to msg.Amount * If in reverse phase: - * Update lot amount -* Extend auction by `BidDuration`, or `MaxEndTime` + * Update Lot amount to msg.Amount +* Extend auction by `BidDuration`, up to `MaxEndTime` From 2537928ee73e7ff5a63cbdc8c9624f9b48f9a381 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Thu, 9 Jan 2020 17:25:16 +0000 Subject: [PATCH 22/27] update doc comments --- x/auction/genesis.go | 2 +- x/auction/keeper/auctions.go | 14 +++++-- x/auction/keeper/keeper.go | 27 ++++++------ x/auction/keeper/params.go | 2 - x/auction/module.go | 15 +++---- x/auction/types/auctions.go | 46 ++++++++++++--------- x/auction/types/codec.go | 1 - x/auction/types/expected_keepers.go | 3 -- x/auction/types/genesis.go | 18 ++++---- x/auction/types/keys.go | 6 ++- x/auction/types/msg.go | 4 +- x/auction/types/params.go | 9 ++-- x/auction/types/{quierier.go => querier.go} | 0 x/auction/types/utils.go | 9 ---- 14 files changed, 75 insertions(+), 81 deletions(-) rename x/auction/types/{quierier.go => querier.go} (100%) delete mode 100644 x/auction/types/utils.go diff --git a/x/auction/genesis.go b/x/auction/genesis.go index 8b4a5f1e..b5fad1ab 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -4,7 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// InitGenesis - initializes the store state from genesis data +// InitGenesis initializes the store state from genesis data. func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetNextAuctionID(ctx, data.NextAuctionID) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index debac160..8c92f3ba 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -9,7 +9,7 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -// StartSurplusAuction starts a normal auction that mints the sold coins. +// StartSurplusAuction starts a new surplus (forward) auction. func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { // create auction auction := types.NewSurplusAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) @@ -27,7 +27,7 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin return auctionID, nil } -// StartDebtAuction starts an auction where sellers compete by offering decreasing prices. +// StartDebtAuction starts a new debt (reverse) auction. func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { // create auction auction := types.NewDebtAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) @@ -45,7 +45,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in return auctionID, nil } -// StartCollateralAuction starts an auction where bidders bid up to a maxBid, then switch to bidding down on price. +// StartCollateralAuction starts a new collateral (2-phase) auction where bidders bid up to a maxBid, then switch to bidding down on the Lot. func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { // create auction weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) @@ -111,6 +111,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres return nil } +// PlaceBidSurplus places a forward bid on a surplus auction, moving coins and returning the updated auction. func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, sdk.Error) { // Validate New Bid if bid.Denom != a.Bid.Denom { @@ -151,6 +152,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder return a, nil } +// PlaceForwardBidCollateral places a forward bid on a collateral auction, moving coins and returning the updated auction. func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, sdk.Error) { // Validate new bid if bid.Denom != a.Bid.Denom { @@ -192,6 +194,7 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc return a, nil } +// PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction. func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, sdk.Error) { // Validate bid if lot.Denom != a.Lot.Denom { @@ -239,6 +242,7 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc return a, nil } +// PlaceBidDebt places a reverse bid on a debt auction, moving coins and returning the updated auction. func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, sdk.Error) { // Validate New Bid if lot.Denom != a.Lot.Denom { @@ -307,6 +311,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { return nil } +// PayoutDebtAuction pays out the proceeds for a debt auction, first minting the coins. func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Error { err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) if err != nil { @@ -319,6 +324,7 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Erro return nil } +// PayoutSurplusAuction pays out the proceeds for a surplus auction. func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sdk.Error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { @@ -327,6 +333,7 @@ func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sd return nil } +// PayoutCollateralAuction pays out the proceeds for a collateral auction. func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) sdk.Error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { @@ -344,6 +351,7 @@ func earliestTime(t1, t2 time.Time) time.Time { } } +// splitCoinIntoWeightedBuckets divides up some amount of coins according to some weights. func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, sdk.Error) { for _, bucket := range buckets { if bucket.IsNegative() { diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index 28e41540..d756463f 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -47,7 +47,7 @@ func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, sdk.Error) { return types.Uint64FromBytes(bz), nil } -// incrementNextAuctionID increments the global ID in the store by 1 +// IncrementNextAuctionID increments the next auction ID in the store by 1. func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { id, err := k.GetNextAuctionID(ctx) if err != nil { @@ -74,13 +74,12 @@ func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, 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 +// SetAuction puts the auction into the store, and updates any indexes. 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()) + k.removeFromIndex(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) } // store auction @@ -89,10 +88,10 @@ func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) { store.Set(types.GetAuctionKey(auction.GetID()), bz) // add to index - k.InsertIntoIndex(ctx, auction.GetEndTime(), auction.GetID()) + k.insertIntoIndex(ctx, auction.GetEndTime(), auction.GetID()) } -// getAuction gets an auction from the store by auctionID +// GetAuction gets an auction from the store. func (k Keeper) GetAuction(ctx sdk.Context, auctionID uint64) (types.Auction, bool) { var auction types.Auction @@ -106,12 +105,12 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID uint64) (types.Auction, bo return auction, true } -// DeleteAuction removes an auction from the store without any validation +// DeleteAuction removes an auction from the store, and any indexes. func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID uint64) { // remove from index auction, found := k.GetAuction(ctx, auctionID) if found { - k.RemoveFromIndex(ctx, auction.GetEndTime(), auctionID) + k.removeFromIndex(ctx, auction.GetEndTime(), auctionID) } // delete auction @@ -119,14 +118,14 @@ func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID uint64) { 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 uint64) { +// insertIntoIndex adds an auction ID and end time into the byTime index. +func (k Keeper) insertIntoIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) - store.Set(types.GetAuctionByTimeKey(endTime, auctionID), types.Uint64ToBytes(auctionID)) // TODO + store.Set(types.GetAuctionByTimeKey(endTime, auctionID), types.Uint64ToBytes(auctionID)) } -// RemoveFromIndex removes an auction ID and end time from the byTime index -func (k Keeper) RemoveFromIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { +// removeFromIndex removes an auction ID and end time from the byTime index. +func (k Keeper) removeFromIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) } @@ -143,7 +142,7 @@ func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time. defer iterator.Close() for ; iterator.Valid(); iterator.Next() { - // TODO get the auction ID - either read from store, or extract from key + auctionID := types.Uint64FromBytes(iterator.Value()) if cb(auctionID) { diff --git a/x/auction/keeper/params.go b/x/auction/keeper/params.go index bd2d67de..6d7ddfd5 100644 --- a/x/auction/keeper/params.go +++ b/x/auction/keeper/params.go @@ -5,12 +5,10 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) -// SetParams sets the auth module's parameters. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSubspace.SetParamSet(ctx, ¶ms) } -// GetParams gets the auth module's parameters. func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { k.paramSubspace.GetParamSet(ctx, ¶ms) return diff --git a/x/auction/module.go b/x/auction/module.go index 7e0f1bd7..14c2d023 100644 --- a/x/auction/module.go +++ b/x/auction/module.go @@ -21,20 +21,20 @@ var ( _ module.AppModuleBasic = AppModuleBasic{} ) -// AppModuleBasic app module basics object +// AppModuleBasic implements the sdk.AppModuleBasic interface. type AppModuleBasic struct{} -// Name get module name +// Name returns the module name. func (AppModuleBasic) Name() string { return ModuleName } -// RegisterCodec register module codec +// RegisterCodec registers the module codec. func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { RegisterCodec(cdc) } -// DefaultGenesis default genesis state +// DefaultGenesis returns the default genesis state. func (AppModuleBasic) DefaultGenesis() json.RawMessage { return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) } @@ -64,7 +64,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { return cli.GetQueryCmd(StoreKey, cdc) } -// AppModule app module type +// AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic keeper Keeper @@ -78,11 +78,6 @@ func NewAppModule(keeper Keeper) AppModule { } } -// Name module name -func (AppModule) Name() string { - return ModuleName -} - // RegisterInvariants performs a no-op. func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index ec685006..9fbd01cd 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -8,28 +8,28 @@ import ( "github.com/cosmos/cosmos-sdk/x/supply" ) -// Auction is an interface to several types of auction. +// Auction is an interface for handling common actions on auctions. type Auction interface { GetID() uint64 WithID(uint64) Auction GetEndTime() time.Time } -// BaseAuction type shared by all Auctions +// BaseAuction is a common type shared by all Auctions. type BaseAuction struct { ID uint64 - Initiator string // Module that starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. - Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) - Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) - Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) - EndTime time.Time // Auction closing time. Triggers at the end of the block with time ≥ endTime (bids placed in that block are valid) // TODO ensure everything is consistent with this + Initiator string // Module name that starts the auction. Pays out Lot. + Lot sdk.Coin // Coins that will paid out by Initiator to the winning bidder. + Bidder sdk.AccAddress // Latest bidder. Receiver of Lot. + Bid sdk.Coin // Coins paid into the auction the bidder. + EndTime time.Time // Current auction closing time. Triggers at the end of the block with time ≥ EndTime. MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } -// GetID getter for auction ID +// GetID is a getter for auction ID. func (a BaseAuction) GetID() uint64 { return a.ID } -// GetEndTime getter for auction end time +// GetEndTime is a getter for auction end time. func (a BaseAuction) GetEndTime() time.Time { return a.EndTime } func (a BaseAuction) String() string { @@ -46,15 +46,15 @@ func (a BaseAuction) String() string { ) } -// SurplusAuction type for forward auctions +// SurplusAuction is a forward auction that burns what it receives as bids. type SurplusAuction struct { BaseAuction } -// WithID returns an auction with the ID set +// WithID returns an auction with the ID set. func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a } -// NewSurplusAuction creates a new forward auction +// NewSurplusAuction returns a new surplus auction. func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction { auction := SurplusAuction{BaseAuction{ // no ID @@ -68,15 +68,15 @@ func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim return auction } -// DebtAuction type for reverse auctions +// DebtAuction is a reverse auction that mints what it pays out. type DebtAuction struct { BaseAuction } -// WithID returns an auction with the ID set +// WithID returns an auction with the ID set. func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a } -// NewDebtAuction creates a new reverse auction +// NewDebtAuction returns a new debt auction. func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) DebtAuction { // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. @@ -93,16 +93,21 @@ func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, E return auction } -// CollateralAuction type for forward reverse auction +// CollateralAuction is a two phase auction. +// Initially, in forward auction phase, bids can be placed up to a max bid. +// Then it switches to a reverse auction phase, where the initial amount up for auction is bidded down. +// Unsold Lot is sent to LotReturns, being divided among the addresses by weight. type CollateralAuction struct { BaseAuction MaxBid sdk.Coin - LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. + LotReturns WeightedAddresses } -// WithID returns an auction with the ID set +// WithID returns an auction with the ID set. func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a } +// IsReversePhase returns whether the auction has switched over to reverse phase or not. +// Auction initially start in forward phase. func (a CollateralAuction) IsReversePhase() bool { return a.Bid.IsEqual(a.MaxBid) } @@ -123,7 +128,7 @@ func (a CollateralAuction) String() string { ) } -// NewCollateralAuction creates a new forward reverse auction +// NewCollateralAuction returns a new collateral auction. func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) CollateralAuction { auction := CollateralAuction{ BaseAuction: BaseAuction{ @@ -140,12 +145,13 @@ func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid return auction } -// WeightedAddresses type for storing an address and its associated weight +// WeightedAddresses is a type for storing some addresses and associated weights. type WeightedAddresses struct { Addresses []sdk.AccAddress Weights []sdk.Int } +// NewWeightedAddresses returns a new list addresses with weights. func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { if len(addrs) != len(weights) { return WeightedAddresses{}, sdk.ErrInternal("number of addresses doesn't match number of weights") diff --git a/x/auction/types/codec.go b/x/auction/types/codec.go index 12f8f9eb..1904f432 100644 --- a/x/auction/types/codec.go +++ b/x/auction/types/codec.go @@ -15,7 +15,6 @@ func init() { func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgPlaceBid{}, "auction/MsgPlaceBid", nil) - // Register the Auction interface and concrete types cdc.RegisterInterface((*Auction)(nil), nil) cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil) cdc.RegisterConcrete(DebtAuction{}, "auction/DebtAuction", nil) diff --git a/x/auction/types/expected_keepers.go b/x/auction/types/expected_keepers.go index 85956576..021b8918 100644 --- a/x/auction/types/expected_keepers.go +++ b/x/auction/types/expected_keepers.go @@ -7,9 +7,6 @@ import ( // SupplyKeeper defines the expected supply Keeper type SupplyKeeper interface { - //GetSupply(ctx sdk.Context) supplyexported.SupplyI - - //GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go index 423d12fd..808ac050 100644 --- a/x/auction/types/genesis.go +++ b/x/auction/types/genesis.go @@ -4,17 +4,17 @@ import ( "bytes" ) -// Auctions type for an array of auctions +// Auctions is a slice of auctions. type Auctions []Auction -// GenesisState - auction state that must be provided at genesis +// GenesisState is auction state that must be provided at chain genesis. type GenesisState struct { - NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` - Params Params `json:"auction_params" yaml:"auction_params"` + NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` + Params Params `json:"auction_params" yaml:"auction_params"` Auctions Auctions `json:"genesis_auctions" yaml:"genesis_auctions"` } -// NewGenesisState returns a new genesis state object for auctions module +// NewGenesisState returns a new genesis state object for auctions module. func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState { return GenesisState{ NextAuctionID: nextID, @@ -23,24 +23,24 @@ func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState { } } -// DefaultGenesisState defines default genesis state for auction module +// DefaultGenesisState returns the default genesis state for auction module. func DefaultGenesisState() GenesisState { return NewGenesisState(0, DefaultParams(), Auctions{}) } -// Equal checks whether two GenesisState structs are equivalent +// Equal checks whether two GenesisState structs are equivalent. func (data GenesisState) Equal(data2 GenesisState) bool { b1 := ModuleCdc.MustMarshalBinaryBare(data) b2 := ModuleCdc.MustMarshalBinaryBare(data2) return bytes.Equal(b1, b2) } -// IsEmpty returns true if a GenesisState is empty +// IsEmpty returns true if a GenesisState is empty. func (data GenesisState) IsEmpty() bool { return data.Equal(GenesisState{}) } -// ValidateGenesis validates genesis inputs. Returns error if validation of any input fails. +// ValidateGenesis validates genesis inputs. It returns error if validation of any input fails. func ValidateGenesis(data GenesisState) error { if err := data.Params.Validate(); err != nil { return err diff --git a/x/auction/types/keys.go b/x/auction/types/keys.go index e63bdf54..78ce6f34 100644 --- a/x/auction/types/keys.go +++ b/x/auction/types/keys.go @@ -26,7 +26,7 @@ var ( AuctionKeyPrefix = []byte{0x00} // prefix for keys that store auctions AuctionByTimeKeyPrefix = []byte{0x01} // prefix for keys that are part of the auctionsByTime index - NextAuctionIDKey = []byte{0x02} + NextAuctionIDKey = []byte{0x02} // key for the next auction id ) func GetAuctionKey(auctionID uint64) []byte { @@ -37,12 +37,14 @@ func GetAuctionByTimeKey(endTime time.Time, auctionID uint64) []byte { return append(sdk.FormatTimeBytes(endTime), Uint64ToBytes(auctionID)...) } +// Uint64FromBytes converts some fixed length bytes back into a uint64. func Uint64FromBytes(bz []byte) uint64 { return binary.BigEndian.Uint64(bz) } +// Uint64ToBytes converts a uint64 into fixed length bytes for use in store keys. func Uint64ToBytes(id uint64) []byte { bz := make([]byte, 8) binary.BigEndian.PutUint64(bz, uint64(id)) return bz -} \ No newline at end of file +} diff --git a/x/auction/types/msg.go b/x/auction/types/msg.go index dbaf43b2..976aedb1 100644 --- a/x/auction/types/msg.go +++ b/x/auction/types/msg.go @@ -8,8 +8,8 @@ var _ sdk.Msg = &MsgPlaceBid{} // MsgPlaceBid is the message type used to place a bid on any type of auction. type MsgPlaceBid struct { AuctionID uint64 - Bidder sdk.AccAddress // This can be a buyer (who increments bid), or a seller (who decrements lot) TODO rename to be clearer? - Amount sdk.Coin // The new bid or lot to set on the auction + Bidder sdk.AccAddress + Amount sdk.Coin // The new bid or lot to be set on the auction. } // NewMsgPlaceBid returns a new MsgPlaceBid. diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 0bbf075d..22d01736 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -25,13 +25,13 @@ var ( var _ subspace.ParamSet = &Params{} -// Params governance parameters for auction module +// Params is the governance parameters for the auction module. type Params struct { MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. } -// NewParams creates a new Params object +// NewParams returns a new Params object. func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params { return Params{ MaxAuctionDuration: maxAuctionDuration, @@ -39,7 +39,7 @@ func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Para } } -// DefaultParams default parameters for auctions +// DefaultParams returns the default parameters for auctions. func DefaultParams() Params { return NewParams( DefaultMaxAuctionDuration, @@ -52,8 +52,7 @@ func ParamKeyTable() subspace.KeyTable { return subspace.NewKeyTable().RegisterParamSet(&Params{}) } -// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs -// pairs of auth module's parameters. +// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs. // nolint func (ap *Params) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ diff --git a/x/auction/types/quierier.go b/x/auction/types/querier.go similarity index 100% rename from x/auction/types/quierier.go rename to x/auction/types/querier.go diff --git a/x/auction/types/utils.go b/x/auction/types/utils.go deleted file mode 100644 index b1aad11e..00000000 --- a/x/auction/types/utils.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -// Go doesn't have a built in min function for integers :( -func min(a, b int64) int64 { - if a < b { - return a - } - return b -} From 00c1a371d2860409b111a9ecfde163101cb304f8 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Fri, 10 Jan 2020 13:13:04 +0000 Subject: [PATCH 23/27] add params validation --- x/auction/types/params.go | 35 +++++++++++++++++++------------ x/auction/types/params_test.go | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 x/auction/types/params_test.go diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 22d01736..5f52f258 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" ) @@ -19,7 +20,7 @@ const ( // Parameter keys var ( // ParamStoreKeyParams Param store key for auction params - KeyAuctionBidDuration = []byte("MaxBidDuration") + KeyAuctionBidDuration = []byte("BidDuration") KeyAuctionDuration = []byte("MaxAuctionDuration") ) @@ -28,14 +29,14 @@ var _ subspace.ParamSet = &Params{} // Params is the governance parameters for the auction module. type Params struct { MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction - MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. + BidDuration time.Duration `json:"bid_duration" yaml:"bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. } // NewParams returns a new Params object. func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params { return Params{ MaxAuctionDuration: maxAuctionDuration, - MaxBidDuration: bidDuration, + BidDuration: bidDuration, } } @@ -54,29 +55,37 @@ func ParamKeyTable() subspace.KeyTable { // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs. // nolint -func (ap *Params) ParamSetPairs() subspace.ParamSetPairs { +func (p *Params) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ - {KeyAuctionBidDuration, &ap.MaxBidDuration}, - {KeyAuctionDuration, &ap.MaxAuctionDuration}, + {KeyAuctionBidDuration, &p.BidDuration}, + {KeyAuctionDuration, &p.MaxAuctionDuration}, } } // Equal returns a boolean determining if two Params types are identical. -func (ap Params) Equal(ap2 Params) bool { - bz1 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&ap) - bz2 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&ap2) +func (p Params) Equal(p2 Params) bool { + bz1 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&p) + bz2 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&p2) return bytes.Equal(bz1, bz2) } // String implements stringer interface -func (ap Params) String() string { +func (p Params) String() string { return fmt.Sprintf(`Auction Params: Max Auction Duration: %s - Max Bid Duration: %s`, ap.MaxAuctionDuration, ap.MaxBidDuration) + Bid Duration: %s`, p.MaxAuctionDuration, p.BidDuration) } // Validate checks that the parameters have valid values. -func (ap Params) Validate() error { - // TODO check durations are within acceptable limits, if needed +func (p Params) Validate() error { + if p.BidDuration < 0 { + return sdk.ErrInternal("bid duration cannot be negative") + } + if p.MaxAuctionDuration < 0 { + return sdk.ErrInternal("max auction duration cannot be negative") + } + if p.BidDuration > p.MaxAuctionDuration { + return sdk.ErrInternal("bid duration param cannot be larger than max auction duration") + } return nil } diff --git a/x/auction/types/params_test.go b/x/auction/types/params_test.go new file mode 100644 index 00000000..0f642875 --- /dev/null +++ b/x/auction/types/params_test.go @@ -0,0 +1,38 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestParams_Validate(t *testing.T) { + type fields struct { + } + testCases := []struct { + name string + MaxAuctionDuration time.Duration + BidDuration time.Duration + expectErr bool + }{ + {"normal", 24 * time.Hour, 1 * time.Hour, false}, + {"negativeBid", 24 * time.Hour, -1 * time.Hour, true}, + {"negativeAuction", -24 * time.Hour, 1 * time.Hour, true}, + {"bid>auction", 1 * time.Hour, 24 * time.Hour, true}, + {"zeros", 0, 0, false}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := Params{ + MaxAuctionDuration: tc.MaxAuctionDuration, + BidDuration: tc.BidDuration, + } + err := p.Validate() + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} From d03509a17ae018feabddb240e33b0309813cbe88 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Fri, 10 Jan 2020 14:08:47 +0000 Subject: [PATCH 24/27] code cleanup, address review comments --- x/auction/abci.go | 15 +- x/auction/abci_test.go | 1 - x/auction/keeper/auctions.go | 101 ++++---- x/auction/keeper/keeper.go | 25 +- x/auction/keeper/keeper_test.go | 2 +- x/auction/types/auctions.go | 9 +- x/auction/types/auctions_test.go | 396 ------------------------------- x/auction/types/keys.go | 11 +- 8 files changed, 89 insertions(+), 471 deletions(-) delete mode 100644 x/auction/types/auctions_test.go diff --git a/x/auction/abci.go b/x/auction/abci.go index 8ce80f90..402c5c12 100644 --- a/x/auction/abci.go +++ b/x/auction/abci.go @@ -6,17 +6,8 @@ import ( // EndBlocker runs at the end of every block. func EndBlocker(ctx sdk.Context, k Keeper) { - - var expiredAuctions []uint64 - k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { - expiredAuctions = append(expiredAuctions, id) - return false - }) - // Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration - for _, id := range expiredAuctions { - err := k.CloseAuction(ctx, id) - if err != nil { - panic(err) - } + err := k.CloseExpiredAuctions(ctx) + if err != nil { + panic(err) } } diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 418ca49a..4ee5cd13 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -22,7 +22,6 @@ func TestKeeper_EndBlocker(t *testing.T) { returnAddrs := addrs[1:] returnWeights := []sdk.Int{sdk.NewInt(1)} sellerModName := liquidator.ModuleName - //sellerAddr := supply.NewModuleAddress(sellerModName) tApp := app.NewTestApp() sellerAcc := supply.NewEmptyModuleAccount(sellerModName) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 8c92f3ba..755027fe 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -11,15 +11,18 @@ import ( // StartSurplusAuction starts a new surplus (forward) auction. func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { - // create auction - auction := types.NewSurplusAuction(seller, lot, bidDenom, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) - // take coins from module account + auction := types.NewSurplusAuction( + seller, + lot, + bidDenom, + ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration)) + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { return 0, err } - // store the auction + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err @@ -29,15 +32,19 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin // StartDebtAuction starts a new debt (reverse) auction. func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { - // create auction - auction := types.NewDebtAuction(buyer, bid, initialLot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration)) + + auction := types.NewDebtAuction( + buyer, + bid, + initialLot, + ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration)) // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) if !macc.HasPermission(supply.Minter) { return 0, sdk.ErrInternal("module does not have minting permissions") } - // store the auction + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err @@ -45,21 +52,20 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in return auctionID, nil } -// StartCollateralAuction starts a new collateral (2-phase) auction where bidders bid up to a maxBid, then switch to bidding down on the Lot. +// StartCollateralAuction starts a new collateral (2-phase) auction. func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { - // create auction + weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) if err != nil { return 0, err } auction := types.NewCollateralAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) - // take coins from module account err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { return 0, err } - // store the auction + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err @@ -70,18 +76,17 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C // PlaceBid places a bid on any auction. func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) sdk.Error { - // get auction from store auction, found := k.GetAuction(ctx, auctionID) if !found { return sdk.ErrInternal("auction doesn't exist") } - // validate + // validation common to all auctions if ctx.BlockTime().After(auction.GetEndTime()) { return sdk.ErrInternal("auction has closed") } - // place bid + // move coins and return updated auction var err sdk.Error var updatedAuction types.Auction switch a := auction.(type) { @@ -106,14 +111,13 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres panic(fmt.Sprintf("unrecognized auction type: %T", auction)) } - // store updated auction k.SetAuction(ctx, updatedAuction) return nil } // PlaceBidSurplus places a forward bid on a surplus auction, moving coins and returning the updated auction. func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, sdk.Error) { - // Validate New Bid + // Validate new bid if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") } @@ -121,9 +125,9 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder return a, sdk.ErrInternal("bid not greater than last bid") } - // Move Coins - if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { // catch edge case of someone updating their bid with a low balance, also don't send if amt is zero - // pay back previous bidder + // New bidder pays back old bidder + // Catch edge cases of a bidder replacing their own bid, and the amount being zero (sending zero coins produces meaningless send events). + if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) if err != nil { return a, err @@ -133,7 +137,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder return a, err } } - // burn increase in bid + // Increase in bid is burned err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) if err != nil { return a, err @@ -146,8 +150,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder // Update Auction a.Bidder = bidder a.Bid = bid - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout return a, nil } @@ -167,9 +170,10 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc if a.MaxBid.IsLT(bid) { return a, sdk.ErrInternal("bid higher than max bid") } - // Move Coins - // pay back previous bidder - if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { // catch edge case of someone updating their bid with a low balance, also don't send if amt is zero + + // New bidder pays back old bidder + // Catch edge cases of a bidder replacing their own bid, and the amount being zero (sending zero coins produces meaningless send events). + if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() { err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) if err != nil { return a, err @@ -179,7 +183,7 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc return a, err } } - // pay increase in bid to auction initiator + // Increase in bid sent to auction initiator err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) if err != nil { return a, err @@ -188,15 +192,14 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc // Update Auction a.Bidder = bidder a.Bid = bid - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout return a, nil } // PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction. func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, sdk.Error) { - // Validate bid + // Validate new bid if lot.Denom != a.Lot.Denom { return a, sdk.ErrInternal("lot denom doesn't match auction") } @@ -210,8 +213,9 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc return a, sdk.ErrInternal("auction in reverse phase, new bid not less than previous amount") } - // Move Coins - if !bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + // New bidder pays back old bidder + // Catch edge cases of a bidder replacing their own bid + if !bidder.Equals(a.Bidder) { err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) if err != nil { return a, err @@ -221,7 +225,8 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc return a, err } } - // FIXME paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids + // Decrease in lot is sent to weighted addresses (normally the CDP depositors) + // TODO paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids - check this can't be gamed. lotPayouts, err := splitCoinIntoWeightedBuckets(a.Lot.Sub(lot), a.LotReturns.Weights) if err != nil { return a, err @@ -236,15 +241,14 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc // Update Auction a.Bidder = bidder a.Lot = lot - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout return a, nil } // PlaceBidDebt places a reverse bid on a debt auction, moving coins and returning the updated auction. func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, sdk.Error) { - // Validate New Bid + // Validate new bid if lot.Denom != a.Lot.Denom { return a, sdk.ErrInternal("lot denom doesn't match auction") } @@ -255,8 +259,9 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac return a, sdk.ErrInternal("lot not smaller than last lot") } - // Move Coins - if !bidder.Equals(a.Bidder) { // catch edge case of someone updating their bid with a low balance + // New bidder pays back old bidder + // Catch edge cases of a bidder replacing their own bid + if !bidder.Equals(a.Bidder) { err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid)) if err != nil { return a, err @@ -270,8 +275,7 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac // Update Auction a.Bidder = bidder a.Lot = lot - // increment timeout - a.EndTime = earliestTime(ctx.BlockTime().Add(types.DefaultBidDuration), a.MaxEndTime) + a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout return a, nil } @@ -279,12 +283,11 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac // CloseAuction closes an auction and distributes funds to the highest bidder. func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { - // get the auction from the store auction, found := k.GetAuction(ctx, auctionID) if !found { return sdk.ErrInternal("auction doesn't exist") } - // error if auction has not reached the end time + if ctx.BlockTime().Before(auction.GetEndTime()) { return sdk.ErrInternal(fmt.Sprintf("auction can't be closed as curent block time (%v) is under auction end time (%v)", ctx.BlockTime(), auction.GetEndTime())) } @@ -342,6 +345,22 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAucti return nil } +// CloseExpiredAuctions finds all auctions that are past (or at) their ending times and closes them, paying out to the highest bidder. +func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) sdk.Error { + var expiredAuctions []uint64 + k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { + expiredAuctions = append(expiredAuctions, id) + return false + }) + // Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration + for _, id := range expiredAuctions { + if err := k.CloseAuction(ctx, id); err != nil { + return err + } + } + return nil +} + // earliestTime returns the earliest of two times. func earliestTime(t1, t2 time.Time) time.Time { if t1.Before(t2) { diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index d756463f..c9ee241b 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -1,12 +1,14 @@ package keeper import ( + "fmt" "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/tendermint/tendermint/libs/log" "github.com/kava-labs/kava/x/auction/types" ) @@ -29,6 +31,11 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl } } +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + // SetNextAuctionID stores an ID to be used for the next created auction func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) { store := ctx.KVStore(k.storeKey) @@ -79,16 +86,14 @@ 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()) + k.removeFromByTimeIndex(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()) + k.InsertIntoByTimeIndex(ctx, auction.GetEndTime(), auction.GetID()) } // GetAuction gets an auction from the store. @@ -107,25 +112,23 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID uint64) (types.Auction, bo // DeleteAuction removes an auction from the store, and any indexes. func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID uint64) { - // remove from index auction, found := k.GetAuction(ctx, auctionID) if found { - k.removeFromIndex(ctx, auction.GetEndTime(), auctionID) + k.removeFromByTimeIndex(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 uint64) { +// InsertIntoByTimeIndex adds an auction ID and end time into the byTime index. +func (k Keeper) InsertIntoByTimeIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { // TODO make private, and find way to make tests work store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Set(types.GetAuctionByTimeKey(endTime, auctionID), types.Uint64ToBytes(auctionID)) } -// removeFromIndex removes an auction ID and end time from the byTime index. -func (k Keeper) removeFromIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { +// removeFromByTimeIndex removes an auction ID and end time from the byTime index. +func (k Keeper) removeFromByTimeIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) store.Delete(types.GetAuctionByTimeKey(endTime, auctionID)) } diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 3c59d9ae..03adaeeb 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -113,7 +113,7 @@ func TestIterateAuctionsByTime(t *testing.T) { {time.Date(9999, time.January, 1, 0, 0, 0, 0, time.UTC), 0}, // distant future } for _, v := range byTimeIndex { - keeper.InsertIntoIndex(ctx, v.endTime, v.auctionID) + keeper.InsertIntoByTimeIndex(ctx, v.endTime, v.auctionID) } // read out values from index up to a cutoff time and check they are as expected diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 9fbd01cd..1db5036e 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -46,7 +46,8 @@ func (a BaseAuction) String() string { ) } -// SurplusAuction is a forward auction that burns what it receives as bids. +// SurplusAuction is a forward auction that burns what it receives from bids. +// It is normally used to sell off excess pegged asset acquired by the CDP system. type SurplusAuction struct { BaseAuction } @@ -69,6 +70,7 @@ func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim } // DebtAuction is a reverse auction that mints what it pays out. +// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral. type DebtAuction struct { BaseAuction } @@ -86,7 +88,7 @@ func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, E Initiator: buyerModAccName, Lot: initialLot, Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. - Bid: bid, // amount that the buyer it buying - doesn't change over course of auction + Bid: bid, // amount that the buyer is buying - doesn't change over course of auction EndTime: EndTime, MaxEndTime: EndTime, }} @@ -95,8 +97,9 @@ func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, E // CollateralAuction is a two phase auction. // Initially, in forward auction phase, bids can be placed up to a max bid. -// Then it switches to a reverse auction phase, where the initial amount up for auction is bidded down. +// Then it switches to a reverse auction phase, where the initial amount up for auction is bid down. // Unsold Lot is sent to LotReturns, being divided among the addresses by weight. +// Collateral auctions are normally used to sell off collateral seized from CDPs. type CollateralAuction struct { BaseAuction MaxBid sdk.Coin diff --git a/x/auction/types/auctions_test.go b/x/auction/types/auctions_test.go deleted file mode 100644 index a4d059d6..00000000 --- a/x/auction/types/auctions_test.go +++ /dev/null @@ -1,396 +0,0 @@ -package types - -// // TODO can this be less verbose? Should PlaceBid() be split into smaller functions? -// // It would be possible to combine all auction tests into one test runner. -// func TesSurplusAuction_PlaceBid(t *testing.T) { -// seller := sdk.AccAddress([]byte("a_seller")) -// buyer1 := sdk.AccAddress([]byte("buyer1")) -// buyer2 := sdk.AccAddress([]byte("buyer2")) -// end := EndTime(10000) -// now := EndTime(10) - -// type args struct { -// currentBlockHeight EndTime -// bidder sdk.AccAddress -// lot sdk.Coin -// bid sdk.Coin -// } -// tests := []struct { -// name string -// auction SurplusAuction -// args args -// expectedOutputs []BankOutput -// expectedInputs []BankInput -// expectedEndTime EndTime -// expectedBidder sdk.AccAddress -// expectedBid sdk.Coin -// expectpass bool -// }{ -// { -// "normal", -// SurplusAuction{BaseAuction{ -// Initiator: seller, -// Lot: c("usdx", 100), -// Bidder: buyer1, -// Bid: c("kava", 6), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, buyer2, c("usdx", 100), c("kava", 10)}, -// []BankOutput{{buyer2, c("kava", 10)}}, -// []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, -// now + DefaultMaxBidDuration, -// buyer2, -// c("kava", 10), -// true, -// }, -// { -// "lowBid", -// SurplusAuction{BaseAuction{ -// Initiator: seller, -// Lot: c("usdx", 100), -// Bidder: buyer1, -// Bid: c("kava", 6), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, buyer2, c("usdx", 100), c("kava", 5)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// buyer1, -// c("kava", 6), -// false, -// }, -// { -// "equalBid", -// SurplusAuction{BaseAuction{ -// Initiator: seller, -// Lot: c("usdx", 100), -// Bidder: buyer1, -// Bid: c("kava", 6), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, buyer2, c("usdx", 100), c("kava", 6)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// buyer1, -// c("kava", 6), -// false, -// }, -// { -// "timeout", -// SurplusAuction{BaseAuction{ -// Initiator: seller, -// Lot: c("usdx", 100), -// Bidder: buyer1, -// Bid: c("kava", 6), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{end + 1, buyer2, c("usdx", 100), c("kava", 10)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// buyer1, -// c("kava", 6), -// false, -// }, -// { -// "hitMaxEndTime", -// SurplusAuction{BaseAuction{ -// Initiator: seller, -// Lot: c("usdx", 100), -// Bidder: buyer1, -// Bid: c("kava", 6), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{end - 1, buyer2, c("usdx", 100), c("kava", 10)}, -// []BankOutput{{buyer2, c("kava", 10)}}, -// []BankInput{{buyer1, c("kava", 6)}, {seller, c("kava", 4)}}, -// end, // end time should be capped at MaxEndTime -// buyer2, -// c("kava", 10), -// true, -// }, -// } -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// // update auction and return in/outputs -// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) - -// // check for err -// if tc.expectpass { -// require.Nil(t, err) -// } else { -// require.NotNil(t, err) -// } -// // check for correct in/outputs -// require.Equal(t, tc.expectedOutputs, outputs) -// require.Equal(t, tc.expectedInputs, inputs) -// // check for correct EndTime, bidder, bid -// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) -// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) -// require.Equal(t, tc.expectedBid, tc.auction.Bid) -// }) -// } -// } - -// func TestDebtAuction_PlaceBid(t *testing.T) { -// buyer := sdk.AccAddress([]byte("a_buyer")) -// seller1 := sdk.AccAddress([]byte("seller1")) -// seller2 := sdk.AccAddress([]byte("seller2")) -// end := EndTime(10000) -// now := EndTime(10) - -// type args struct { -// currentBlockHeight EndTime -// bidder sdk.AccAddress -// lot sdk.Coin -// bid sdk.Coin -// } -// tests := []struct { -// name string -// auction DebtAuction -// args args -// expectedOutputs []BankOutput -// expectedInputs []BankInput -// expectedEndTime EndTime -// expectedBidder sdk.AccAddress -// expectedLot sdk.Coin -// expectpass bool -// }{ -// { -// "normal", -// DebtAuction{BaseAuction{ -// Initiator: buyer, -// Lot: c("kava", 10), -// Bidder: seller1, -// Bid: c("usdx", 100), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, seller2, c("kava", 9), c("usdx", 100)}, -// []BankOutput{{seller2, c("usdx", 100)}}, -// []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, -// now + DefaultMaxBidDuration, -// seller2, -// c("kava", 9), -// true, -// }, -// { -// "highBid", -// DebtAuction{BaseAuction{ -// Initiator: buyer, -// Lot: c("kava", 10), -// Bidder: seller1, -// Bid: c("usdx", 100), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, seller2, c("kava", 11), c("usdx", 100)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// seller1, -// c("kava", 10), -// false, -// }, -// { -// "equalBid", -// DebtAuction{BaseAuction{ -// Initiator: buyer, -// Lot: c("kava", 10), -// Bidder: seller1, -// Bid: c("usdx", 100), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{now, seller2, c("kava", 10), c("usdx", 100)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// seller1, -// c("kava", 10), -// false, -// }, -// { -// "timeout", -// DebtAuction{BaseAuction{ -// Initiator: buyer, -// Lot: c("kava", 10), -// Bidder: seller1, -// Bid: c("usdx", 100), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{end + 1, seller2, c("kava", 9), c("usdx", 100)}, -// []BankOutput{}, -// []BankInput{}, -// end, -// seller1, -// c("kava", 10), -// false, -// }, -// { -// "hitMaxEndTime", -// DebtAuction{BaseAuction{ -// Initiator: buyer, -// Lot: c("kava", 10), -// Bidder: seller1, -// Bid: c("usdx", 100), -// EndTime: end, -// MaxEndTime: end, -// }}, -// args{end - 1, seller2, c("kava", 9), c("usdx", 100)}, -// []BankOutput{{seller2, c("usdx", 100)}}, -// []BankInput{{seller1, c("usdx", 100)}, {buyer, c("kava", 1)}}, -// end, // end time should be capped at MaxEndTime -// seller2, -// c("kava", 9), -// true, -// }, -// } -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// // update auction and return in/outputs -// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) - -// // check for err -// if tc.expectpass { -// require.Nil(t, err) -// } else { -// require.NotNil(t, err) -// } -// // check for correct in/outputs -// require.Equal(t, tc.expectedOutputs, outputs) -// require.Equal(t, tc.expectedInputs, inputs) -// // check for correct EndTime, bidder, bid -// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) -// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) -// require.Equal(t, tc.expectedLot, tc.auction.Lot) -// }) -// } -// } - -// func TestCollateralAuction_PlaceBid(t *testing.T) { -// cdpOwner := sdk.AccAddress([]byte("a_cdp_owner")) -// seller := sdk.AccAddress([]byte("a_seller")) -// buyer1 := sdk.AccAddress([]byte("buyer1")) -// buyer2 := sdk.AccAddress([]byte("buyer2")) -// end := EndTime(10000) -// now := EndTime(10) - -// type args struct { -// currentBlockHeight EndTime -// bidder sdk.AccAddress -// lot sdk.Coin -// bid sdk.Coin -// } -// tests := []struct { -// name string -// auction CollateralAuction -// args args -// expectedOutputs []BankOutput -// expectedInputs []BankInput -// expectedEndTime EndTime -// expectedBidder sdk.AccAddress -// expectedLot sdk.Coin -// expectedBid sdk.Coin -// expectpass bool -// }{ -// { -// "normalForwardBid", -// CollateralAuction{BaseAuction: BaseAuction{ -// Initiator: seller, -// Lot: c("xrp", 100), -// Bidder: buyer1, -// Bid: c("usdx", 5), -// EndTime: end, -// MaxEndTime: end}, -// MaxBid: c("usdx", 10), -// OtherPerson: cdpOwner, -// }, -// args{now, buyer2, c("xrp", 100), c("usdx", 6)}, -// []BankOutput{{buyer2, c("usdx", 6)}}, -// []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 1)}}, -// now + DefaultMaxBidDuration, -// buyer2, -// c("xrp", 100), -// c("usdx", 6), -// true, -// }, -// { -// "normalSwitchOverBid", -// CollateralAuction{BaseAuction: BaseAuction{ -// Initiator: seller, -// Lot: c("xrp", 100), -// Bidder: buyer1, -// Bid: c("usdx", 5), -// EndTime: end, -// MaxEndTime: end}, -// MaxBid: c("usdx", 10), -// OtherPerson: cdpOwner, -// }, -// args{now, buyer2, c("xrp", 99), c("usdx", 10)}, -// []BankOutput{{buyer2, c("usdx", 10)}}, -// []BankInput{{buyer1, c("usdx", 5)}, {seller, c("usdx", 5)}, {cdpOwner, c("xrp", 1)}}, -// now + DefaultMaxBidDuration, -// buyer2, -// c("xrp", 99), -// c("usdx", 10), -// true, -// }, -// { -// "normalDebtBid", -// CollateralAuction{BaseAuction: BaseAuction{ -// Initiator: seller, -// Lot: c("xrp", 99), -// Bidder: buyer1, -// Bid: c("usdx", 10), -// EndTime: end, -// MaxEndTime: end}, -// MaxBid: c("usdx", 10), -// OtherPerson: cdpOwner, -// }, -// args{now, buyer2, c("xrp", 90), c("usdx", 10)}, -// []BankOutput{{buyer2, c("usdx", 10)}}, -// []BankInput{{buyer1, c("usdx", 10)}, {cdpOwner, c("xrp", 9)}}, -// now + DefaultMaxBidDuration, -// buyer2, -// c("xrp", 90), -// c("usdx", 10), -// true, -// }, -// // TODO more test cases -// } -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// // update auction and return in/outputs -// outputs, inputs, err := tc.auction.PlaceBid(tc.args.currentBlockHeight, tc.args.bidder, tc.args.lot, tc.args.bid) - -// // check for err -// if tc.expectpass { -// require.Nil(t, err) -// } else { -// require.NotNil(t, err) -// } -// // check for correct in/outputs -// require.Equal(t, tc.expectedOutputs, outputs) -// require.Equal(t, tc.expectedInputs, inputs) -// // check for correct EndTime, bidder, bid -// require.Equal(t, tc.expectedEndTime, tc.auction.EndTime) -// require.Equal(t, tc.expectedBidder, tc.auction.Bidder) -// require.Equal(t, tc.expectedLot, tc.auction.Lot) -// require.Equal(t, tc.expectedBid, tc.auction.Bid) -// }) -// } -// } - -// // defined to avoid cluttering test cases with long function name -// func c(denom string, amount int64) sdk.Coin { -// return sdk.NewInt64Coin(denom, amount) -// } diff --git a/x/auction/types/keys.go b/x/auction/types/keys.go index 78ce6f34..aca635a9 100644 --- a/x/auction/types/keys.go +++ b/x/auction/types/keys.go @@ -21,7 +21,6 @@ const ( DefaultParamspace = ModuleName ) -// TODO use cont to keep immutability? var ( AuctionKeyPrefix = []byte{0x00} // prefix for keys that store auctions AuctionByTimeKeyPrefix = []byte{0x01} // prefix for keys that are part of the auctionsByTime index @@ -37,14 +36,14 @@ func GetAuctionByTimeKey(endTime time.Time, auctionID uint64) []byte { return append(sdk.FormatTimeBytes(endTime), Uint64ToBytes(auctionID)...) } -// Uint64FromBytes converts some fixed length bytes back into a uint64. -func Uint64FromBytes(bz []byte) uint64 { - return binary.BigEndian.Uint64(bz) -} - // Uint64ToBytes converts a uint64 into fixed length bytes for use in store keys. func Uint64ToBytes(id uint64) []byte { bz := make([]byte, 8) binary.BigEndian.PutUint64(bz, uint64(id)) return bz } + +// Uint64FromBytes converts some fixed length bytes back into a uint64. +func Uint64FromBytes(bz []byte) uint64 { + return binary.BigEndian.Uint64(bz) +} From 65ef8a9ba38ce4ba121be4709780949d907ab451 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Fri, 10 Jan 2020 18:55:48 +0100 Subject: [PATCH 25/27] resolve minor TODOs --- x/auction/keeper/auctions.go | 4 ++-- x/auction/keeper/auctions_test.go | 2 +- x/auction/keeper/keeper.go | 4 ---- x/auction/keeper/math_test.go | 1 - 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 755027fe..cecbadd5 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -121,7 +121,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder if bid.Denom != a.Bid.Denom { return a, sdk.ErrInternal("bid denom doesn't match auction") } - if !a.Bid.IsLT(bid) { // TODO add minimum bid size + if !a.Bid.IsLT(bid) { return a, sdk.ErrInternal("bid not greater than last bid") } @@ -255,7 +255,7 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac if lot.IsNegative() { return a, sdk.ErrInternal("lot less than 0") } - if !lot.IsLT(a.Lot) { // TODO add min bid decrements + if !lot.IsLT(a.Lot) { return a, sdk.ErrInternal("lot not smaller than last lot") } diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 6e4ffeb4..197e1c66 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -202,7 +202,7 @@ func TestStartSurplusAuction(t *testing.T) { initialLiquidatorCoins := cs(c("stable", 100)) tApp := app.NewTestApp() - liqAcc := supply.NewEmptyModuleAccount(liquidator.ModuleName, supply.Burner) // TODO could add test to check for burner permissions + liqAcc := supply.NewEmptyModuleAccount(liquidator.ModuleName, supply.Burner) require.NoError(t, liqAcc.SetCoins(initialLiquidatorCoins)) tApp.InitializeFromGenesisStates( NewAuthGenStateFromAccs(authexported.GenesisAccounts{liqAcc}), diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index c9ee241b..cd0b01c2 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -18,7 +18,6 @@ type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec paramSubspace subspace.Subspace - // TODO codespace } // NewKeeper returns a new auction keeper. @@ -43,12 +42,10 @@ func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) { } // 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) (uint64, 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.Uint64FromBytes(bz), nil @@ -135,7 +132,6 @@ func (k Keeper) removeFromByTimeIndex(ctx sdk.Context, endTime time.Time, auctio // 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 uint64) (stop bool)) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) iterator := store.Iterator( diff --git a/x/auction/keeper/math_test.go b/x/auction/keeper/math_test.go index 58e33e7e..ee42c594 100644 --- a/x/auction/keeper/math_test.go +++ b/x/auction/keeper/math_test.go @@ -18,7 +18,6 @@ func TestSplitIntIntoWeightedBuckets(t *testing.T) { {"100split1,9", i(100), is(1, 9), is(10, 90)}, {"7split1,2", i(7), is(1, 2), is(2, 5)}, {"17split1,1,1", i(17), is(1, 1, 1), is(6, 6, 5)}, - // TODO more tests } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { From 27f3e76da3cb90b2f7e2024e9a61151435ec882a Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Fri, 10 Jan 2020 18:57:38 +0100 Subject: [PATCH 26/27] sync spec with code --- x/auction/spec/02_state.md | 28 +++++++++++++++++----------- x/auction/spec/05_params.md | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/x/auction/spec/02_state.md b/x/auction/spec/02_state.md index 075cf08c..306665f9 100644 --- a/x/auction/spec/02_state.md +++ b/x/auction/spec/02_state.md @@ -33,37 +33,43 @@ type Auction interface { GetEndTime() time.Time } -// BaseAuction type shared by all Auctions +// BaseAuction is a common type shared by all Auctions. type BaseAuction struct { ID uint64 - Initiator string // Module that starts the auction. Giving away Lot (aka seller in a forward auction). Restricted to being a module account name rather than any account. - Lot sdk.Coin // Amount of coins up being given by initiator (FA - amount for sale by seller, RA - cost of good by buyer (bid)) - Bidder sdk.AccAddress // Person who bids in the auction. Receiver of Lot. (aka buyer in forward auction, seller in RA) - Bid sdk.Coin // Amount of coins being given by the bidder (FA - bid, RA - amount being sold) - EndTime time.Time // Auction closing time. Triggers at the end of the block with time ≥ endTime (bids placed in that block are valid) // TODO ensure everything is consistent with this + Initiator string // Module name that starts the auction. Pays out Lot. + Lot sdk.Coin // Coins that will paid out by Initiator to the winning bidder. + Bidder sdk.AccAddress // Latest bidder. Receiver of Lot. + Bid sdk.Coin // Coins paid into the auction the bidder. + EndTime time.Time // Current auction closing time. Triggers at the end of the block with time ≥ EndTime. MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. } -// SurplusAuction type for forward auctions +// SurplusAuction is a forward auction that burns what it receives from bids. +// It is normally used to sell off excess pegged asset acquired by the CDP system. type SurplusAuction struct { BaseAuction } -// DebtAuction type for reverse auctions +// DebtAuction is a reverse auction that mints what it pays out. +// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral. type DebtAuction struct { BaseAuction } -// WeightedAddresses type for storing an address and its associated weight +// WeightedAddresses is a type for storing some addresses and associated weights. type WeightedAddresses struct { Addresses []sdk.AccAddress Weights []sdk.Int } -// CollateralAuction type for forward reverse auction +// CollateralAuction is a two phase auction. +// Initially, in forward auction phase, bids can be placed up to a max bid. +// Then it switches to a reverse auction phase, where the initial amount up for auction is bid down. +// Unsold Lot is sent to LotReturns, being divided among the addresses by weight. +// Collateral auctions are normally used to sell off collateral seized from CDPs. type CollateralAuction struct { BaseAuction MaxBid sdk.Coin - LotReturns WeightedAddresses // return addresses to pay out reductions in the lot amount to. Lot is bid down during reverse phase. + LotReturns WeightedAddresses } ``` diff --git a/x/auction/spec/05_params.md b/x/auction/spec/05_params.md index f6751bc1..9a108303 100644 --- a/x/auction/spec/05_params.md +++ b/x/auction/spec/05_params.md @@ -5,4 +5,4 @@ The auction module contains the following parameters: | Key | Type | Example | | ------------------ | ---------------------- | -----------| | MaxAuctionDuration | string (time.Duration) | "48h0m0s" | -| MaxBidDuration | string (time.Duration) | "3h0m0s" | +| BidDuration | string (time.Duration) | "3h0m0s" | From 61e5de556cfdf6fdf842110c6bf20541d04ef437 Mon Sep 17 00:00:00 2001 From: rhuairahrighairigh Date: Sun, 12 Jan 2020 15:17:47 +0100 Subject: [PATCH 27/27] add debt tracking to auctions --- x/auction/abci_test.go | 4 +- x/auction/keeper/auctions.go | 65 ++++++++++++++++++++++++++++--- x/auction/keeper/auctions_test.go | 22 ++++++----- x/auction/keeper/keeper_test.go | 4 +- x/auction/types/auctions.go | 35 ++++++++++------- 5 files changed, 96 insertions(+), 34 deletions(-) diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 4ee5cd13..d147c768 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -25,7 +25,7 @@ func TestKeeper_EndBlocker(t *testing.T) { tApp := app.NewTestApp() sellerAcc := supply.NewEmptyModuleAccount(sellerModName) - require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) + require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100)))) tApp.InitializeFromGenesisStates( NewAuthGenStateFromAccs(authexported.GenesisAccounts{ auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), @@ -36,7 +36,7 @@ func TestKeeper_EndBlocker(t *testing.T) { ctx := tApp.NewContext(true, abci.Header{}) keeper := tApp.GetAuctionKeeper() - auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) + auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40)) require.NoError(t, err) require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30))) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index cecbadd5..50979e26 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -31,13 +31,14 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin } // StartDebtAuction starts a new debt (reverse) auction. -func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { +func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error) { auction := types.NewDebtAuction( buyer, bid, initialLot, - ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration)) + ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration), + debt) // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) @@ -45,6 +46,11 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in return 0, sdk.ErrInternal("module does not have minting permissions") } + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, buyer, types.ModuleName, sdk.NewCoins(debt)) + if err != nil { + return 0, err + } + auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { return 0, err @@ -53,18 +59,28 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in } // StartCollateralAuction starts a new collateral (2-phase) auction. -func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { +func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error) { weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) if err != nil { return 0, err } - auction := types.NewCollateralAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) + auction := types.NewCollateralAuction( + seller, + lot, + ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), + maxBid, + weightedAddresses, + debt) err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { return 0, err } + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(debt)) + if err != nil { + return 0, err + } auctionID, err := k.StoreNewAuction(ctx, auction) if err != nil { @@ -184,10 +200,24 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc } } // Increase in bid sent to auction initiator - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) + bidIncrement := bid.Sub(a.Bid) + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bidIncrement)) if err != nil { return a, err } + // Debt coins are sent to liquidator (until there is no CorrespondingDebt left). Amount sent is equal to bidIncrement. + if a.CorrespondingDebt.IsPositive() { + + debtAmountToReturn := sdk.MinInt(bidIncrement.Amount, a.CorrespondingDebt.Amount) + debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn) + + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn)) + if err != nil { + return a, err + } + a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above + // TODO optionally burn out debt and stable just returned to liquidator + } // Update Auction a.Bidder = bidder @@ -271,6 +301,19 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac return a, err } } + // Debt coins are sent to liquidator the first time a bid is placed. Amount sent is equal to Bid. + if a.Bidder.Equals(supply.NewModuleAddress(a.Initiator)) { + + debtAmountToReturn := sdk.MinInt(a.Bid.Amount, a.CorrespondingDebt.Amount) + debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn) + + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn)) + if err != nil { + return a, err + } + a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above + // TODO optionally burn out debt and stable just returned to liquidator + } // Update Auction a.Bidder = bidder @@ -324,6 +367,12 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Erro if err != nil { return err } + if a.CorrespondingDebt.IsPositive() { + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt)) + if err != nil { + return err + } + } return nil } @@ -342,6 +391,12 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAucti if err != nil { return err } + if a.CorrespondingDebt.IsPositive() { + err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt)) + if err != nil { + return err + } + } return nil } diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 197e1c66..9a45b036 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -69,27 +69,29 @@ func TestDebtAuctionBasic(t *testing.T) { tApp := app.NewTestApp() + buyerAcc := supply.NewEmptyModuleAccount(buyerModName, supply.Minter) // reverse auctions mint payout + require.NoError(t, buyerAcc.SetCoins(cs(c("debt", 100)))) tApp.InitializeFromGenesisStates( NewAuthGenStateFromAccs(authexported.GenesisAccounts{ auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), - supply.NewEmptyModuleAccount(buyerModName, supply.Minter), // reverse auctions mint payout + buyerAcc, }), ) ctx := tApp.NewContext(false, abci.Header{}) keeper := tApp.GetAuctionKeeper() // Start auction - auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999)) // buyer, bid, initialLot + auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999), c("debt", 20)) require.NoError(t, err) - // Check buyer's coins have not decreased, as lot is minted at the end - tApp.CheckBalance(t, ctx, buyerAddr, nil) // zero coins + // Check buyer's coins have not decreased (except for debt), as lot is minted at the end + tApp.CheckBalance(t, ctx, buyerAddr, cs(c("debt", 80))) // Place a bid require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token2", 10))) // Check seller's coins have decreased tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) // Check buyer's coins have increased - tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20))) + tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20), c("debt", 100))) // Close auction at just after auction expiry ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) @@ -109,7 +111,7 @@ func TestCollateralAuctionBasic(t *testing.T) { tApp := app.NewTestApp() sellerAcc := supply.NewEmptyModuleAccount(sellerModName) - require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) + require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100)))) tApp.InitializeFromGenesisStates( NewAuthGenStateFromAccs(authexported.GenesisAccounts{ auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), @@ -123,17 +125,17 @@ func TestCollateralAuctionBasic(t *testing.T) { keeper := tApp.GetAuctionKeeper() // Start auction - auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) // seller, lot, maxBid, otherPerson + auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40)) require.NoError(t, err) // Check seller's coins have decreased - tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100), c("debt", 60))) // Place a forward bid require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10))) // Check bidder's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) // Check seller's coins have increased - tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110))) + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110), c("debt", 70))) // Check return addresses have not received coins for _, ra := range returnAddrs { tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100))) @@ -145,7 +147,7 @@ func TestCollateralAuctionBasic(t *testing.T) { // Check bidder's coins have decreased tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) // Check seller's coins have increased - tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150))) + tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150), c("debt", 100))) // Check return addresses have received coins tApp.CheckBalance(t, ctx, returnAddrs[0], cs(c("token1", 102), c("token2", 100))) tApp.CheckBalance(t, ctx, returnAddrs[1], cs(c("token1", 102), c("token2", 100))) diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 03adaeeb..4b811bb9 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -74,8 +74,8 @@ func TestIterateAuctions(t *testing.T) { auctions := []types.Auction{ types.NewSurplusAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0), - types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(1), - types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}).WithID(2), + types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("debt", 12345678)).WithID(1), + types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}, c("debt", 12345678)).WithID(2), } for _, a := range auctions { keeper.SetAuction(ctx, a) diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 1db5036e..beec6d24 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -73,25 +73,28 @@ func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim // It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral. type DebtAuction struct { BaseAuction + CorrespondingDebt sdk.Coin } // WithID returns an auction with the ID set. func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a } // NewDebtAuction returns a new debt auction. -func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) DebtAuction { +func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time, debt sdk.Coin) DebtAuction { // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. // This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed. - auction := DebtAuction{BaseAuction{ - // no ID - Initiator: buyerModAccName, - Lot: initialLot, - Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. - Bid: bid, // amount that the buyer is buying - doesn't change over course of auction - EndTime: EndTime, - MaxEndTime: EndTime, - }} + auction := DebtAuction{ + BaseAuction: BaseAuction{ + // no ID + Initiator: buyerModAccName, + Lot: initialLot, + Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. + Bid: bid, // amount that the buyer is buying - doesn't change over course of auction + EndTime: EndTime, + MaxEndTime: EndTime}, + CorrespondingDebt: debt, + } return auction } @@ -102,8 +105,9 @@ func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, E // Collateral auctions are normally used to sell off collateral seized from CDPs. type CollateralAuction struct { BaseAuction - MaxBid sdk.Coin - LotReturns WeightedAddresses + CorrespondingDebt sdk.Coin + MaxBid sdk.Coin + LotReturns WeightedAddresses } // WithID returns an auction with the ID set. @@ -132,7 +136,7 @@ func (a CollateralAuction) String() string { } // NewCollateralAuction returns a new collateral auction. -func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) CollateralAuction { +func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction { auction := CollateralAuction{ BaseAuction: BaseAuction{ // no ID @@ -142,8 +146,9 @@ func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid Bid: sdk.NewInt64Coin(maxBid.Denom, 0), EndTime: EndTime, MaxEndTime: EndTime}, - MaxBid: maxBid, - LotReturns: lotReturns, + CorrespondingDebt: debt, + MaxBid: maxBid, + LotReturns: lotReturns, } return auction }