2019-12-12 00:16:10 +00:00
package keeper
import (
"fmt"
2019-12-21 01:04:04 +00:00
"time"
2019-12-12 00:16:10 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/auction/types"
)
2020-01-09 17:25:16 +00:00
// StartSurplusAuction starts a new surplus (forward) auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) StartSurplusAuction ( ctx sdk . Context , seller string , lot sdk . Coin , bidDenom string ) ( uint64 , sdk . Error ) {
2019-12-12 00:16:10 +00:00
2020-01-10 14:08:47 +00:00
auction := types . NewSurplusAuction (
seller ,
lot ,
bidDenom ,
2020-01-14 14:00:37 +00:00
types . DistantFuture )
2020-01-10 14:08:47 +00:00
2019-12-12 00:16:10 +00:00
err := k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( lot ) )
if err != nil {
return 0 , err
}
2020-01-10 14:08:47 +00:00
2019-12-31 11:56:39 +00:00
auctionID , err := k . StoreNewAuction ( ctx , auction )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionStart ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . GetID ( ) ) ) ,
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . Name ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidDenom , auction . Bid . Denom ) ,
sdk . NewAttribute ( types . AttributeKeyLotDenom , auction . Lot . Denom ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return auctionID , nil
}
2020-01-09 17:25:16 +00:00
// StartDebtAuction starts a new debt (reverse) auction.
2020-01-12 14:17:47 +00:00
func ( k Keeper ) StartDebtAuction ( ctx sdk . Context , buyer string , bid sdk . Coin , initialLot sdk . Coin , debt sdk . Coin ) ( uint64 , sdk . Error ) {
2020-01-10 14:08:47 +00:00
auction := types . NewDebtAuction (
buyer ,
bid ,
initialLot ,
2020-01-14 14:00:37 +00:00
types . DistantFuture ,
2020-01-12 14:17:47 +00:00
debt )
2019-12-12 00:16:10 +00:00
// 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 )
2020-01-09 13:55:45 +00:00
if ! macc . HasPermission ( supply . Minter ) {
2020-01-15 10:39:55 +00:00
return 0 , types . ErrInvalidModulePermissions ( k . codespace , supply . Minter )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
2020-01-12 14:17:47 +00:00
err := k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , buyer , types . ModuleName , sdk . NewCoins ( debt ) )
if err != nil {
return 0 , err
}
2019-12-31 11:56:39 +00:00
auctionID , err := k . StoreNewAuction ( ctx , auction )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionStart ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . GetID ( ) ) ) ,
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . Name ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidDenom , auction . Bid . Denom ) ,
sdk . NewAttribute ( types . AttributeKeyLotDenom , auction . Lot . Denom ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return auctionID , nil
}
2020-01-10 14:08:47 +00:00
// StartCollateralAuction starts a new collateral (2-phase) auction.
2020-01-12 14:17:47 +00:00
func ( k Keeper ) StartCollateralAuction ( ctx sdk . Context , seller string , lot sdk . Coin , maxBid sdk . Coin , lotReturnAddrs [ ] sdk . AccAddress , lotReturnWeights [ ] sdk . Int , debt sdk . Coin ) ( uint64 , sdk . Error ) {
2020-01-10 14:08:47 +00:00
2019-12-31 11:10:15 +00:00
weightedAddresses , err := types . NewWeightedAddresses ( lotReturnAddrs , lotReturnWeights )
if err != nil {
return 0 , err
}
2020-01-12 14:17:47 +00:00
auction := types . NewCollateralAuction (
seller ,
lot ,
2020-01-14 14:00:37 +00:00
types . DistantFuture ,
2020-01-12 14:17:47 +00:00
maxBid ,
weightedAddresses ,
debt )
2019-12-12 00:16:10 +00:00
2019-12-31 11:10:15 +00:00
err = k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2020-01-12 14:17:47 +00:00
err = k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( debt ) )
if err != nil {
return 0 , err
}
2020-01-10 14:08:47 +00:00
2019-12-31 11:56:39 +00:00
auctionID , err := k . StoreNewAuction ( ctx , auction )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionStart ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . GetID ( ) ) ) ,
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . Name ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidDenom , auction . Bid . Denom ) ,
sdk . NewAttribute ( types . AttributeKeyLotDenom , auction . Lot . Denom ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return auctionID , nil
}
// PlaceBid places a bid on any auction.
2020-01-09 14:58:47 +00:00
func ( k Keeper ) PlaceBid ( ctx sdk . Context , auctionID uint64 , bidder sdk . AccAddress , newAmount sdk . Coin ) sdk . Error {
2019-12-12 00:16:10 +00:00
auction , found := k . GetAuction ( ctx , auctionID )
if ! found {
2020-01-15 10:39:55 +00:00
return types . ErrAuctionNotFound ( k . codespace , auctionID )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// validation common to all auctions
2019-12-12 00:16:10 +00:00
if ctx . BlockTime ( ) . After ( auction . GetEndTime ( ) ) {
2020-01-15 10:39:55 +00:00
return types . ErrAuctionHasExpired ( k . codespace , auctionID )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// move coins and return updated auction
2019-12-12 00:16:10 +00:00
var err sdk . Error
2020-01-09 15:43:42 +00:00
var updatedAuction types . Auction
switch a := auction . ( type ) {
2020-01-09 16:09:19 +00:00
case types . SurplusAuction :
if updatedAuction , err = k . PlaceBidSurplus ( ctx , a , bidder , newAmount ) ; err != nil {
2019-12-12 00:16:10 +00:00
return err
}
2020-01-09 16:09:19 +00:00
case types . DebtAuction :
if updatedAuction , err = k . PlaceBidDebt ( ctx , a , bidder , newAmount ) ; err != nil {
2019-12-12 00:16:10 +00:00
return err
}
2020-01-09 16:09:19 +00:00
case types . CollateralAuction :
2020-01-09 15:43:42 +00:00
if ! a . IsReversePhase ( ) {
2020-01-09 16:09:19 +00:00
updatedAuction , err = k . PlaceForwardBidCollateral ( ctx , a , bidder , newAmount )
2020-01-09 13:55:45 +00:00
} else {
2020-01-09 16:09:19 +00:00
updatedAuction , err = k . PlaceReverseBidCollateral ( ctx , a , bidder , newAmount )
2020-01-09 13:55:45 +00:00
}
2019-12-12 00:16:10 +00:00
if err != nil {
return err
}
default :
2020-01-15 10:39:55 +00:00
return types . ErrUnrecognizedAuctionType ( k . codespace )
2019-12-12 00:16:10 +00:00
}
2020-01-09 15:43:42 +00:00
k . SetAuction ( ctx , updatedAuction )
2020-01-14 14:00:37 +00:00
2019-12-12 00:16:10 +00:00
return nil
}
2020-01-09 17:25:16 +00:00
// PlaceBidSurplus places a forward bid on a surplus auction, moving coins and returning the updated auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PlaceBidSurplus ( ctx sdk . Context , a types . SurplusAuction , bidder sdk . AccAddress , bid sdk . Coin ) ( types . SurplusAuction , sdk . Error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2019-12-12 00:16:10 +00:00
if bid . Denom != a . Bid . Denom {
2020-01-15 10:39:55 +00:00
return a , types . ErrInvalidBidDenom ( k . codespace , bid . Denom , a . Bid . Denom )
2019-12-12 00:16:10 +00:00
}
2020-01-10 17:55:48 +00:00
if ! a . Bid . IsLT ( bid ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrBidTooSmall ( k . codespace , bid , a . Bid )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// New bidder pays back old bidder
2020-01-14 14:00:37 +00:00
// Catch edge cases of a bidder replacing their own bid, or the amount being zero (sending zero coins produces meaningless send events).
2020-01-10 14:08:47 +00:00
if ! bidder . Equals ( a . Bidder ) && ! a . Bid . IsZero ( ) {
2020-01-09 13:55:45 +00:00
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// Increase in bid is burned
2020-01-09 13:55:45 +00:00
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , a . Initiator , sdk . NewCoins ( bid . Sub ( a . Bid ) ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return a , err
}
2020-01-09 13:55:45 +00:00
err = k . supplyKeeper . BurnCoins ( ctx , a . Initiator , sdk . NewCoins ( bid . Sub ( a . Bid ) ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return a , err
}
2020-01-09 13:55:45 +00:00
// Update Auction
a . Bidder = bidder
a . Bid = bid
2020-01-14 14:00:37 +00:00
if ! a . HasReceivedBids {
a . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
}
a . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . BidDuration ) , a . MaxEndTime ) // increment timeout, up to MaxEndTime
a . HasReceivedBids = true
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , a . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , a . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidAmount , a . Bid . Amount . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , a . EndTime . Unix ( ) ) ) ,
) ,
)
2020-01-09 13:55:45 +00:00
return a , nil
}
2020-01-09 17:25:16 +00:00
// PlaceForwardBidCollateral places a forward bid on a collateral auction, moving coins and returning the updated auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PlaceForwardBidCollateral ( ctx sdk . Context , a types . CollateralAuction , bidder sdk . AccAddress , bid sdk . Coin ) ( types . CollateralAuction , sdk . Error ) {
2020-01-09 15:43:42 +00:00
// Validate new bid
2020-01-09 14:58:47 +00:00
if bid . Denom != a . Bid . Denom {
2020-01-15 10:39:55 +00:00
return a , types . ErrInvalidBidDenom ( k . codespace , bid . Denom , a . Bid . Denom )
2020-01-09 14:58:47 +00:00
}
2020-01-09 13:55:45 +00:00
if a . IsReversePhase ( ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrCollateralAuctionIsInReversePhase ( k . codespace , a . ID )
2020-01-09 13:55:45 +00:00
}
if ! a . Bid . IsLT ( bid ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrBidTooSmall ( k . codespace , bid , a . Bid )
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
if a . MaxBid . IsLT ( bid ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrBidTooLarge ( k . codespace , bid , a . MaxBid )
2020-01-09 13:55:45 +00:00
}
2020-01-10 14:08:47 +00:00
// New bidder pays back old bidder
// Catch edge cases of a bidder replacing their own bid, and the amount being zero (sending zero coins produces meaningless send events).
if ! bidder . Equals ( a . Bidder ) && ! a . Bid . IsZero ( ) {
2020-01-09 13:55:45 +00:00
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
}
2020-01-10 14:08:47 +00:00
// Increase in bid sent to auction initiator
2020-01-12 14:17:47 +00:00
bidIncrement := bid . Sub ( a . Bid )
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , a . Initiator , sdk . NewCoins ( bidIncrement ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return a , err
}
2020-01-21 17:41:37 +00:00
// Debt coins are sent to liquidator (until there is no CorrespondingDebt left). Amount sent is equal to bidIncrement (or whatever is left if < bidIncrement).
2020-01-12 14:17:47 +00:00
if a . CorrespondingDebt . IsPositive ( ) {
debtAmountToReturn := sdk . MinInt ( bidIncrement . Amount , a . CorrespondingDebt . Amount )
debtToReturn := sdk . NewCoin ( a . CorrespondingDebt . Denom , debtAmountToReturn )
err = k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , a . Initiator , sdk . NewCoins ( debtToReturn ) )
if err != nil {
return a , err
}
a . CorrespondingDebt = a . CorrespondingDebt . Sub ( debtToReturn ) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
}
2019-12-12 00:16:10 +00:00
// Update Auction
a . Bidder = bidder
a . Bid = bid
2020-01-14 14:00:37 +00:00
if ! a . HasReceivedBids {
a . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
}
a . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . BidDuration ) , a . MaxEndTime ) // increment timeout, up to MaxEndTime
a . HasReceivedBids = true
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , a . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , a . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidAmount , a . Bid . Amount . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , a . EndTime . Unix ( ) ) ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return a , nil
}
2020-01-09 17:25:16 +00:00
// PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PlaceReverseBidCollateral ( ctx sdk . Context , a types . CollateralAuction , bidder sdk . AccAddress , lot sdk . Coin ) ( types . CollateralAuction , sdk . Error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2020-01-09 14:58:47 +00:00
if lot . Denom != a . Lot . Denom {
2020-01-15 10:39:55 +00:00
return a , types . ErrInvalidLotDenom ( k . codespace , lot . Denom , a . Lot . Denom )
2020-01-09 14:58:47 +00:00
}
2020-01-09 13:55:45 +00:00
if ! a . IsReversePhase ( ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrCollateralAuctionIsInForwardPhase ( k . codespace , a . ID )
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
if ! lot . IsLT ( a . Lot ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrLotTooLarge ( k . codespace , lot , a . Lot )
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
2020-01-10 14:08:47 +00:00
// New bidder pays back old bidder
// Catch edge cases of a bidder replacing their own bid
if ! bidder . Equals ( a . Bidder ) {
2020-01-09 13:55:45 +00:00
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// Decrease in lot is sent to weighted addresses (normally the CDP depositors)
// TODO paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids - check this can't be gamed.
2020-01-09 13:55:45 +00:00
lotPayouts , err := splitCoinIntoWeightedBuckets ( a . Lot . Sub ( lot ) , a . LotReturns . Weights )
2019-12-12 00:16:10 +00:00
if err != nil {
return a , err
}
2019-12-31 11:10:15 +00:00
for i , payout := range lotPayouts {
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . LotReturns . Addresses [ i ] , sdk . NewCoins ( payout ) )
if err != nil {
return a , err
}
}
2019-12-12 00:16:10 +00:00
// Update Auction
a . Bidder = bidder
a . Lot = lot
2020-01-14 14:00:37 +00:00
if ! a . HasReceivedBids {
a . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
}
a . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . BidDuration ) , a . MaxEndTime ) // increment timeout, up to MaxEndTime
a . HasReceivedBids = true
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , a . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , a . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyLotAmount , a . Lot . Amount . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , a . EndTime . Unix ( ) ) ) ,
) ,
)
2019-12-12 00:16:10 +00:00
2019-12-28 18:46:53 +00:00
return a , nil
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
2020-01-09 17:25:16 +00:00
// PlaceBidDebt places a reverse bid on a debt auction, moving coins and returning the updated auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PlaceBidDebt ( ctx sdk . Context , a types . DebtAuction , bidder sdk . AccAddress , lot sdk . Coin ) ( types . DebtAuction , sdk . Error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2019-12-12 00:16:10 +00:00
if lot . Denom != a . Lot . Denom {
2020-01-15 10:39:55 +00:00
return a , types . ErrInvalidLotDenom ( k . codespace , lot . Denom , a . Lot . Denom )
2019-12-12 00:16:10 +00:00
}
2020-01-10 17:55:48 +00:00
if ! lot . IsLT ( a . Lot ) {
2020-01-15 10:39:55 +00:00
return a , types . ErrLotTooLarge ( k . codespace , lot , a . Lot )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// New bidder pays back old bidder
// Catch edge cases of a bidder replacing their own bid
if ! bidder . Equals ( a . Bidder ) {
2020-01-09 13:55:45 +00:00
err := k . supplyKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Bid ) )
if err != nil {
return a , err
}
2019-12-12 00:16:10 +00:00
}
2020-01-21 17:41:37 +00:00
// Debt coins are sent to liquidator the first time a bid is placed. Amount sent is equal to min of Bid and amount of debt.
2020-01-12 14:17:47 +00:00
if a . Bidder . Equals ( supply . NewModuleAddress ( a . Initiator ) ) {
debtAmountToReturn := sdk . MinInt ( a . Bid . Amount , a . CorrespondingDebt . Amount )
debtToReturn := sdk . NewCoin ( a . CorrespondingDebt . Denom , debtAmountToReturn )
err := k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , a . Initiator , sdk . NewCoins ( debtToReturn ) )
if err != nil {
return a , err
}
a . CorrespondingDebt = a . CorrespondingDebt . Sub ( debtToReturn ) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
}
2019-12-12 00:16:10 +00:00
// Update Auction
a . Bidder = bidder
a . Lot = lot
2020-01-14 14:00:37 +00:00
if ! a . HasReceivedBids {
a . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
}
a . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . BidDuration ) , a . MaxEndTime ) // increment timeout, up to MaxEndTime
a . HasReceivedBids = true
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , a . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , a . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyLotAmount , a . Lot . Amount . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , a . EndTime . Unix ( ) ) ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return a , nil
}
// CloseAuction closes an auction and distributes funds to the highest bidder.
2020-01-01 14:11:19 +00:00
func ( k Keeper ) CloseAuction ( ctx sdk . Context , auctionID uint64 ) sdk . Error {
2019-12-12 00:16:10 +00:00
auction , found := k . GetAuction ( ctx , auctionID )
if ! found {
2020-01-15 10:39:55 +00:00
return types . ErrAuctionNotFound ( k . codespace , auctionID )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
2019-12-12 00:16:10 +00:00
if ctx . BlockTime ( ) . Before ( auction . GetEndTime ( ) ) {
2020-01-15 10:39:55 +00:00
return types . ErrAuctionHasNotExpired ( k . codespace , ctx . BlockTime ( ) , auction . GetEndTime ( ) )
2019-12-12 00:16:10 +00:00
}
// payout to the last bidder
switch auc := auction . ( type ) {
2020-01-09 16:09:19 +00:00
case types . SurplusAuction :
if err := k . PayoutSurplusAuction ( ctx , auc ) ; err != nil {
2019-12-12 00:16:10 +00:00
return err
}
2020-01-09 16:09:19 +00:00
case types . DebtAuction :
if err := k . PayoutDebtAuction ( ctx , auc ) ; err != nil {
2020-01-09 15:43:42 +00:00
return err
}
2020-01-09 16:09:19 +00:00
case types . CollateralAuction :
if err := k . PayoutCollateralAuction ( ctx , auc ) ; err != nil {
2019-12-12 00:16:10 +00:00
return err
}
default :
2020-01-15 10:39:55 +00:00
return types . ErrUnrecognizedAuctionType ( k . codespace )
2019-12-12 00:16:10 +00:00
}
k . DeleteAuction ( ctx , auctionID )
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionClose ,
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . GetID ( ) ) ) ,
) ,
)
2019-12-12 00:16:10 +00:00
return nil
}
2020-01-09 15:43:42 +00:00
2020-01-09 17:25:16 +00:00
// PayoutDebtAuction pays out the proceeds for a debt auction, first minting the coins.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PayoutDebtAuction ( ctx sdk . Context , a types . DebtAuction ) sdk . Error {
2019-12-12 00:16:10 +00:00
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
}
2020-01-12 14:17:47 +00:00
if a . CorrespondingDebt . IsPositive ( ) {
err = k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , a . Initiator , sdk . NewCoins ( a . CorrespondingDebt ) )
if err != nil {
return err
}
}
2019-12-12 00:16:10 +00:00
return nil
}
2020-01-09 15:43:42 +00:00
2020-01-09 17:25:16 +00:00
// PayoutSurplusAuction pays out the proceeds for a surplus auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PayoutSurplusAuction ( ctx sdk . Context , a types . SurplusAuction ) sdk . Error {
2020-01-09 15:43:42 +00:00
err := k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Lot ) )
if err != nil {
return err
}
return nil
}
2020-01-09 17:25:16 +00:00
// PayoutCollateralAuction pays out the proceeds for a collateral auction.
2020-01-09 16:09:19 +00:00
func ( k Keeper ) PayoutCollateralAuction ( ctx sdk . Context , a types . CollateralAuction ) sdk . Error {
2020-01-09 15:43:42 +00:00
err := k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , a . Bidder , sdk . NewCoins ( a . Lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return err
}
2020-01-12 14:17:47 +00:00
if a . CorrespondingDebt . IsPositive ( ) {
err = k . supplyKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , a . Initiator , sdk . NewCoins ( a . CorrespondingDebt ) )
if err != nil {
return err
}
}
2019-12-12 00:16:10 +00:00
return nil
}
2019-12-21 01:04:04 +00:00
2020-01-10 14:08:47 +00:00
// CloseExpiredAuctions finds all auctions that are past (or at) their ending times and closes them, paying out to the highest bidder.
func ( k Keeper ) CloseExpiredAuctions ( ctx sdk . Context ) sdk . Error {
var expiredAuctions [ ] uint64
k . IterateAuctionsByTime ( ctx , ctx . BlockTime ( ) , func ( id uint64 ) bool {
expiredAuctions = append ( expiredAuctions , id )
return false
} )
// Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration
for _ , id := range expiredAuctions {
if err := k . CloseAuction ( ctx , id ) ; err != nil {
return err
}
}
return nil
}
2019-12-28 18:46:53 +00:00
// earliestTime returns the earliest of two times.
2019-12-21 01:04:04 +00:00
func earliestTime ( t1 , t2 time . Time ) time . Time {
2019-12-28 18:46:53 +00:00
if t1 . Before ( t2 ) {
return t1
} else {
return t2 // also returned if times are equal
}
2019-12-21 01:04:04 +00:00
}
2019-12-31 11:10:15 +00:00
2020-01-09 17:25:16 +00:00
// splitCoinIntoWeightedBuckets divides up some amount of coins according to some weights.
2019-12-31 11:10:15 +00:00
func splitCoinIntoWeightedBuckets ( coin sdk . Coin , buckets [ ] sdk . Int ) ( [ ] sdk . Coin , sdk . Error ) {
for _ , bucket := range buckets {
if bucket . IsNegative ( ) {
return nil , sdk . ErrInternal ( "cannot split coin into bucket with negative weight" )
}
}
amounts := splitIntIntoWeightedBuckets ( coin . Amount , buckets )
result := make ( [ ] sdk . Coin , len ( amounts ) )
for i , a := range amounts {
result [ i ] = sdk . NewCoin ( coin . Denom , a )
}
return result , nil
}