rough auction type refactor

This commit is contained in:
rhuairahrighairigh 2019-12-11 22:59:06 +00:00
parent f794ba1bf9
commit d8a428e1d8
4 changed files with 381 additions and 211 deletions

View File

@ -24,7 +24,6 @@ const (
var ( var (
// functions aliases // functions aliases
NewIDFromString = types.NewIDFromString NewIDFromString = types.NewIDFromString
NewBaseAuction = types.NewBaseAuction
NewForwardAuction = types.NewForwardAuction NewForwardAuction = types.NewForwardAuction
NewReverseAuction = types.NewReverseAuction NewReverseAuction = types.NewReverseAuction
NewForwardReverseAuction = types.NewForwardReverseAuction NewForwardReverseAuction = types.NewForwardReverseAuction

View File

@ -7,11 +7,13 @@ import (
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params/subspace" "github.com/cosmos/cosmos-sdk/x/params/subspace"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/auction/types" "github.com/kava-labs/kava/x/auction/types"
) )
type Keeper struct { type Keeper struct {
bankKeeper types.BankKeeper supplyKeeper types.SupplyKeeper
storeKey sdk.StoreKey storeKey sdk.StoreKey
cdc *codec.Codec cdc *codec.Codec
paramSubspace subspace.Subspace paramSubspace subspace.Subspace
@ -19,23 +21,27 @@ type Keeper struct {
} }
// NewKeeper returns a new auction keeper. // 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{ return Keeper{
bankKeeper: bankKeeper, supplyKeeper: supplyKeeper,
storeKey: storeKey, storeKey: storeKey,
cdc: cdc, cdc: cdc,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), 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. // 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 // create auction
auction, initiatorOutput := types.NewForwardAuction(seller, lot, initialBid, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) auction := types.NewForwardAuction(seller, lot, bidDenom, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration)
// start the auction
auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) // 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 { if err != nil {
return 0, err 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. // 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 // create auction
auction, initiatorOutput := types.NewReverseAuction(buyer, bid, initialLot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration) auction := types.NewReverseAuction(buyer, bid, initialLot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration)
// start the auction
auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) // 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 { if err != nil {
return 0, err 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. // 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 // create auction
initialBid := sdk.NewInt64Coin(maxBid.Denom, 0) // set the bidding coin denomination from the specified max bid auction := types.NewForwardReverseAuction(seller, lot, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration, maxBid, otherPerson)
auction, initiatorOutput := types.NewForwardReverseAuction(seller, lot, initialBid, types.EndTime(ctx.BlockHeight())+types.DefaultMaxAuctionDuration, maxBid, otherPerson)
// start the auction // take coins from module account
auctionID, err := k.startAuction(ctx, &auction, initiatorOutput) 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 { if err != nil {
return 0, err return 0, err
} }
return auctionID, nil 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 // get ID
newAuctionID, err := k.getNextAuctionID(ctx) newAuctionID, err := k.getNextAuctionID(ctx)
if err != nil { if err != nil {
@ -76,18 +94,14 @@ func (k Keeper) startAuction(ctx sdk.Context, auction types.Auction, initiatorOu
// set ID // set ID
auction.SetID(newAuctionID) 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 // store auction
k.SetAuction(ctx, auction) k.SetAuction(ctx, auction)
k.incrementNextAuctionID(ctx) k.incrementNextAuctionID(ctx)
return newAuctionID, nil return newAuctionID, nil
} }
// ==============================================================================================================================
// PlaceBid places a bid on any auction. // 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 { 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") return sdk.ErrInternal("auction doesn't exist")
} }
// place bid // check end time
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 ctx.BlockHeight() > 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 { if err != nil {
return err return err
} }
// TODO this will fail if someone tries to update their bid without the full bid amount sitting in their account case types.ReverseAuction:
// sub outputs a, err = k.PlaceBidReverse(ctx, auc, bidder, lot)
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
if err != nil { if err != nil {
panic(err) return err
} }
} case types.ForwardReverseAuction:
// add inputs a, err = k.PlaceBidForwardReverse(ctx, auc, bidder, bid, lot)
for _, input := range coinInputs {
_, err = k.bankKeeper.AddCoins(ctx, input.Address, sdk.NewCoins(input.Coin)) // TODO errors
if err != nil { if err != nil {
panic(err) return err
} }
default:
panic("unrecognized auction type")
} }
// store updated auction // store updated auction
k.SetAuction(ctx, auction) k.SetAuction(ctx, a) // maybe move into above funcs
return nil return nil
} }
// CloseAuction closes an auction and distributes funds to the seller and highest bidder. func (k Keeper) PlaceBidForward(ctx sdk.Context, a types.ForwardAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.ForwardAuction, sdk.Error) {
// 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? // 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 { func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error {
// get the auction from the store // get the auction from the store
@ -134,22 +292,52 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error {
return sdk.ErrInternal("auction doesn't exist") return sdk.ErrInternal("auction doesn't exist")
} }
// error if auction has not reached the end time // 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())) 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 // payout to the last bidder
coinInput := auction.GetPayout() var err sdk.Error
_, err := k.bankKeeper.AddCoins(ctx, coinInput.Address, sdk.NewCoins(coinInput.Coin)) switch auc := auction.(type) {
case types.ForwardAuction, types.ForwardReverseAuction:
err = k.PayoutAuctionLot(ctx, auc)
if err != nil { if err != nil {
return err 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) // Delete auction from store (and queue)
k.DeleteAuction(ctx, auctionID) k.DeleteAuction(ctx, auctionID)
return nil 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 ---------- // ---------- Store methods ----------
// Use these to add and remove auction from the store. // 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 // if not found, set the id at 0
bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0))
store.Set(k.getNextAuctionIDKey(), bz) 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") //return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
} }
var auctionID types.ID var auctionID types.ID
@ -177,7 +365,7 @@ func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error {
bz := store.Get(k.getNextAuctionIDKey()) bz := store.Get(k.getNextAuctionIDKey())
if bz == nil { if bz == nil {
panic("initial auctionID never set in genesis") 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 var auctionID types.ID
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID) 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) store := ctx.KVStore(k.storeKey)
bz := store.Get(k.getAuctionKey(auctionID)) bz := store.Get(k.getAuctionKey(auctionID))
if bz == nil { 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) k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auction)

View File

@ -5,26 +5,26 @@ import (
"strconv" "strconv"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply"
) )
// Auction is an interface to several types of auction. // Auction is an interface to several types of auction.
type Auction interface { type Auction interface {
GetID() ID GetID() ID
SetID(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) GetEndTime() EndTime // auctions close at the end of the block with blockheight EndTime (ie bids placed in that block are valid)
GetPayout() BankInput // GetPayout() BankInput
String() string
} }
// BaseAuction type shared by all Auctions // BaseAuction type shared by all Auctions
type BaseAuction struct { type BaseAuction struct {
ID ID 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)) 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) 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) 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. 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 // 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 // SetID setter for auction ID
func (a *BaseAuction) SetID(id ID) { a.ID = 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 // 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 // GetPayout implements Auction
func (a BaseAuction) GetPayout() BankInput { // func (a BaseAuction) GetPayout() BankInput {
return BankInput{a.Bidder, a.Lot} // 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 (e EndTime) String() string { func (e EndTime) String() string {
return string(e) return string(e)
} }
func (a BaseAuction) String() string { func (a *BaseAuction) String() string {
return fmt.Sprintf(`Auction %d: return fmt.Sprintf(`Auction %d:
Initiator: %s Initiator: %s
Lot: %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 // ForwardAuction type for forward auctions
type ForwardAuction struct { type ForwardAuction struct {
BaseAuction *BaseAuction
} }
// NewForwardAuction creates a new forward auction // NewForwardAuction creates a new forward auction
func NewForwardAuction(seller sdk.AccAddress, lot sdk.Coin, initialBid sdk.Coin, EndTime EndTime) (ForwardAuction, BankOutput) { func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime EndTime) ForwardAuction {
auction := ForwardAuction{BaseAuction{ auction := ForwardAuction{&BaseAuction{
// no ID // no ID
Initiator: seller, Initiator: seller,
Lot: lot, Lot: lot,
Bidder: seller, // send the proceeds from the first bid back to the seller 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: initialBid, // set this to zero most of the time Bid: sdk.NewInt64Coin(bidDenom, 0),
EndTime: EndTime, EndTime: EndTime,
MaxEndTime: EndTime, MaxEndTime: EndTime,
}} }}
output := BankOutput{seller, lot} // output := BankOutput{seller, lot}
return auction, output return auction
} }
// PlaceBid implements Auction // PlaceBid implements Auction
func (a *ForwardAuction) PlaceBid(currentBlockHeight EndTime, bidder sdk.AccAddress, lot sdk.Coin, bid sdk.Coin) ([]BankOutput, []BankInput, sdk.Error) { // 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? // // TODO check lot size matches lot?
// check auction has not closed // // check auction has not closed
if currentBlockHeight > a.EndTime { // if currentBlockHeight > a.EndTime {
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed")
} // }
// check bid is greater than last bid // // check bid is greater than last bid
if !a.Bid.IsLT(bid) { // TODO add minimum bid size // if !a.Bid.IsLT(bid) { // TODO add minimum bid size
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid")
} // }
// calculate coin movements // // calculate coin movements
outputs := []BankOutput{{bidder, bid}} // new bidder pays bid now // 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 // inputs := []BankInput{{a.Bidder, a.Bid}, {a.Initiator, bid.Sub(a.Bid)}} // old bidder is paid back, extra goes to seller
// update auction // // update auction
a.Bidder = bidder // a.Bidder = bidder
a.Bid = bid // a.Bid = bid
// increment timeout // TODO into keeper? // // 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? // 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 // 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 // TODO when exporting state and initializing a new genesis, we'll need a way to differentiate forward from reverse auctions
type ReverseAuction struct { type ReverseAuction struct {
BaseAuction *BaseAuction
} }
// NewReverseAuction creates a new reverse auction // NewReverseAuction creates a new reverse auction
func NewReverseAuction(buyer sdk.AccAddress, bid sdk.Coin, initialLot sdk.Coin, EndTime EndTime) (ReverseAuction, BankOutput) { func NewReverseAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime EndTime) ReverseAuction {
auction := ReverseAuction{BaseAuction{ // 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 // no ID
Initiator: buyer, Initiator: buyerModAccName,
Lot: initialLot, Lot: initialLot,
Bidder: buyer, // send proceeds from the first bid to the buyer 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 it buying - doesn't change over course of auction
EndTime: EndTime, EndTime: EndTime,
MaxEndTime: EndTime, MaxEndTime: EndTime,
}} }}
output := BankOutput{buyer, initialLot} //output := BankOutput{buyer, initialLot}
return auction, output return auction
} }
// PlaceBid implements 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 bid size matches bid?
// check auction has not closed // // check auction has not closed
if currentBlockHeight > a.EndTime { // if currentBlockHeight > a.EndTime {
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed")
} // }
// check bid is less than last bid // // check bid is less than last bid
if !lot.IsLT(a.Lot) { // TODO add min bid decrements // if !lot.IsLT(a.Lot) { // TODO add min bid decrements
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot")
} // }
// calculate coin movements // // calculate coin movements
outputs := []BankOutput{{bidder, a.Bid}} // new bidder pays bid now // 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 // 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 // // update auction
a.Bidder = bidder // a.Bidder = bidder
a.Lot = lot // a.Lot = lot
// increment timeout // TODO into keeper? // // 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? // 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 // ForwardReverseAuction type for forward reverse auction
type ForwardReverseAuction struct { type ForwardReverseAuction struct {
BaseAuction *BaseAuction
MaxBid sdk.Coin 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: return fmt.Sprintf(`Auction %d:
Initiator: %s Initiator: %s
Lot: %s Lot: %s
@ -239,69 +211,69 @@ func (a ForwardReverseAuction) String() string {
} }
// NewForwardReverseAuction creates a new forward reverse auction // 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{ auction := ForwardReverseAuction{
BaseAuction: BaseAuction{ BaseAuction: &BaseAuction{
// no ID // no ID
Initiator: seller, Initiator: seller,
Lot: lot, Lot: lot,
Bidder: seller, // send the proceeds from the first bid back to the seller 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: initialBid, // 0 most of the time Bid: sdk.NewInt64Coin(maxBid.Denom, 0),
EndTime: EndTime, EndTime: EndTime,
MaxEndTime: EndTime}, MaxEndTime: EndTime},
MaxBid: maxBid, MaxBid: maxBid,
OtherPerson: otherPerson, OtherPerson: otherPerson,
} }
output := BankOutput{seller, lot} //output := BankOutput{seller, lot}
return auction, output return auction
} }
// PlaceBid implements 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) { // 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 // // check auction has not closed
if currentBlockHeight > a.EndTime { // if currentBlockHeight > a.EndTime {
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("auction has closed")
} // }
// determine phase of auction // // determine phase of auction
switch { // switch {
case a.Bid.IsLT(a.MaxBid) && bid.IsLT(a.MaxBid): // case a.Bid.IsLT(a.MaxBid) && bid.IsLT(a.MaxBid):
// Forward auction phase // // Forward auction phase
if !a.Bid.IsLT(bid) { // TODO add min bid increments // if !a.Bid.IsLT(bid) { // TODO add min bid increments
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid not greater than last bid")
} // }
outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now // 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 // 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): // case a.Bid.IsLT(a.MaxBid):
// Switch over phase // // Switch over phase
if !bid.IsEqual(a.MaxBid) { // require bid == a.MaxBid // if !bid.IsEqual(a.MaxBid) { // require bid == a.MaxBid
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid greater than the max bid") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("bid greater than the max bid")
} // }
outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now // outputs = []BankOutput{{bidder, bid}} // new bidder pays bid now
inputs = []BankInput{ // inputs = []BankInput{
{a.Bidder, a.Bid}, // old bidder is paid back // {a.Bidder, a.Bid}, // old bidder is paid back
{a.Initiator, bid.Sub(a.Bid)}, // extra goes to seller // {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 // {a.OtherPerson, a.Lot.Sub(lot)}, //decrease in price for goes to original CDP owner
} // }
case a.Bid.IsEqual(a.MaxBid): // case a.Bid.IsEqual(a.MaxBid):
// Reverse auction phase // // Reverse auction phase
if !lot.IsLT(a.Lot) { // TODO add min bid decrements // if !lot.IsLT(a.Lot) { // TODO add min bid decrements
return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot") // return []BankOutput{}, []BankInput{}, sdk.ErrInternal("lot not smaller than last lot")
} // }
outputs = []BankOutput{{bidder, a.Bid}} // new bidder pays bid now // 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 // 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: // default:
panic("should never be reached") // TODO // panic("should never be reached") // TODO
} // }
// update auction // // update auction
a.Bidder = bidder // a.Bidder = bidder
a.Lot = lot // a.Lot = lot
a.Bid = bid // a.Bid = bid
// increment timeout // // increment timeout
// TODO use bid duration param // // 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? // 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
} // }

View File

@ -2,9 +2,20 @@ package types
import ( import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
) )
type BankKeeper interface { // SupplyKeeper defines the expected supply Keeper
SubtractCoins(sdk.Context, sdk.AccAddress, sdk.Coins) (sdk.Coins, sdk.Error) type SupplyKeeper interface {
AddCoins(sdk.Context, sdk.AccAddress, sdk.Coins) (sdk.Coins, sdk.Error) //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
} }