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 (
// functions aliases
NewIDFromString = types.NewIDFromString
NewBaseAuction = types.NewBaseAuction
NewForwardAuction = types.NewForwardAuction
NewReverseAuction = types.NewReverseAuction
NewForwardReverseAuction = types.NewForwardReverseAuction

View File

@ -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)

View File

@ -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
// }

View File

@ -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
}