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 }