2019-12-12 00:16:10 +00:00
package keeper
import (
2020-06-17 09:09:44 +00:00
"errors"
2019-12-12 00:16:10 +00:00
"fmt"
2019-12-21 01:04:04 +00:00
"time"
2019-12-12 00:16:10 +00:00
2023-04-05 23:21:59 +00:00
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
2019-12-12 00:16:10 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
2022-01-08 00:39:27 +00:00
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
2020-04-30 14:13:31 +00:00
2019-12-12 00:16:10 +00:00
"github.com/kava-labs/kava/x/auction/types"
)
2020-01-09 17:25:16 +00:00
// StartSurplusAuction starts a new surplus (forward) auction.
2020-04-23 16:35:58 +00:00
func ( k Keeper ) StartSurplusAuction ( ctx sdk . Context , seller string , lot sdk . Coin , bidDenom string ) ( uint64 , error ) {
2020-01-10 14:08:47 +00:00
auction := types . NewSurplusAuction (
seller ,
lot ,
bidDenom ,
2020-04-23 16:35:58 +00:00
types . DistantFuture ,
)
2020-01-10 14:08:47 +00:00
2020-05-04 17:52:36 +00:00
// NOTE: for the duration of the auction the auction module account holds the lot
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2020-01-10 14:08:47 +00:00
2022-01-08 00:39:27 +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 ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auctionID ) ) ,
2020-01-29 14:42:03 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . GetType ( ) ) ,
2020-05-08 15:07:11 +00:00
sdk . NewAttribute ( types . AttributeKeyBid , auction . Bid . String ( ) ) ,
2020-05-04 17:52:36 +00:00
sdk . NewAttribute ( types . AttributeKeyLot , auction . Lot . String ( ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
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-04-23 16:35:58 +00:00
func ( k Keeper ) StartDebtAuction ( ctx sdk . Context , buyer string , bid sdk . Coin , initialLot sdk . Coin , debt sdk . Coin ) ( uint64 , 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-05-04 17:52:36 +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.
2022-01-08 00:39:27 +00:00
macc := k . accountKeeper . GetModuleAccount ( ctx , buyer )
if ! macc . HasPermission ( authtypes . Minter ) {
panic ( fmt . Errorf ( "module '%s' does not have '%s' permission" , buyer , authtypes . Minter ) )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
2020-05-04 17:52:36 +00:00
// NOTE: for the duration of the auction the auction module account holds the debt
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromModuleToModule ( ctx , buyer , types . ModuleName , sdk . NewCoins ( debt ) )
2020-01-12 14:17:47 +00:00
if err != nil {
return 0 , err
}
2022-01-08 00:39:27 +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 ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auctionID ) ) ,
2020-01-29 14:42:03 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . GetType ( ) ) ,
2020-05-08 15:07:11 +00:00
sdk . NewAttribute ( types . AttributeKeyBid , auction . Bid . String ( ) ) ,
2020-05-04 17:52:36 +00:00
sdk . NewAttribute ( types . AttributeKeyLot , auction . Lot . String ( ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
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-04-23 16:35:58 +00:00
func ( k Keeper ) StartCollateralAuction (
ctx sdk . Context , seller string , lot , maxBid sdk . Coin ,
2023-04-05 23:21:59 +00:00
lotReturnAddrs [ ] sdk . AccAddress , lotReturnWeights [ ] sdkmath . Int , debt sdk . Coin ,
2020-04-23 16:35:58 +00:00
) ( uint64 , error ) {
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 ,
2020-04-23 16:35:58 +00:00
debt ,
)
2019-12-12 00:16:10 +00:00
2020-05-04 17:52:36 +00:00
// NOTE: for the duration of the auction the auction module account holds the debt and the lot
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return 0 , err
}
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToModule ( ctx , seller , types . ModuleName , sdk . NewCoins ( debt ) )
2020-01-12 14:17:47 +00:00
if err != nil {
return 0 , err
}
2020-01-10 14:08:47 +00:00
2022-01-08 00:39:27 +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 ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auctionID ) ) ,
2020-01-29 14:42:03 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionType , auction . GetType ( ) ) ,
2020-05-08 15:07:11 +00:00
sdk . NewAttribute ( types . AttributeKeyBid , auction . Bid . String ( ) ) ,
2020-05-04 17:52:36 +00:00
sdk . NewAttribute ( types . AttributeKeyLot , auction . Lot . String ( ) ) ,
2020-05-08 15:07:11 +00:00
sdk . NewAttribute ( types . AttributeKeyMaxBid , auction . MaxBid . String ( ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
2019-12-12 00:16:10 +00:00
return auctionID , nil
}
// PlaceBid places a bid on any auction.
2020-04-23 16:35:58 +00:00
func ( k Keeper ) PlaceBid ( ctx sdk . Context , auctionID uint64 , bidder sdk . AccAddress , newAmount sdk . Coin ) error {
2019-12-12 00:16:10 +00:00
auction , found := k . GetAuction ( ctx , auctionID )
if ! found {
2023-04-05 23:21:59 +00:00
return errorsmod . Wrapf ( types . ErrAuctionNotFound , "%d" , 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 ( ) ) {
2023-04-05 23:21:59 +00:00
return errorsmod . Wrapf ( types . ErrAuctionHasExpired , "%d" , auctionID )
2019-12-12 00:16:10 +00:00
}
2020-01-10 14:08:47 +00:00
// move coins and return updated auction
2020-04-23 16:35:58 +00:00
var (
err error
updatedAuction types . Auction
)
2020-06-11 19:23:58 +00:00
switch auctionType := auction . ( type ) {
2022-01-08 00:39:27 +00:00
case * types . SurplusAuction :
2020-06-11 19:23:58 +00:00
updatedAuction , err = k . PlaceBidSurplus ( ctx , auctionType , bidder , newAmount )
2022-01-08 00:39:27 +00:00
case * types . DebtAuction :
2020-06-11 19:23:58 +00:00
updatedAuction , err = k . PlaceBidDebt ( ctx , auctionType , bidder , newAmount )
2022-01-08 00:39:27 +00:00
case * types . CollateralAuction :
2020-06-11 19:23:58 +00:00
if ! auctionType . IsReversePhase ( ) {
updatedAuction , err = k . PlaceForwardBidCollateral ( ctx , auctionType , bidder , newAmount )
2020-01-09 13:55:45 +00:00
} else {
2020-06-11 19:23:58 +00:00
updatedAuction , err = k . PlaceReverseBidCollateral ( ctx , auctionType , bidder , newAmount )
2020-01-09 13:55:45 +00:00
}
2019-12-12 00:16:10 +00:00
default :
2023-04-05 23:21:59 +00:00
err = errorsmod . Wrap ( types . ErrUnrecognizedAuctionType , auction . GetType ( ) )
2020-04-23 16:35:58 +00:00
}
if err != nil {
return err
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.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PlaceBidSurplus ( ctx sdk . Context , auction * types . SurplusAuction , bidder sdk . AccAddress , bid sdk . Coin ) ( * types . SurplusAuction , error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2020-06-11 19:23:58 +00:00
if bid . Denom != auction . Bid . Denom {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrInvalidBidDenom , "%s ≠ %s" , bid . Denom , auction . Bid . Denom )
2019-12-12 00:16:10 +00:00
}
2020-06-11 19:23:58 +00:00
minNewBidAmt := auction . Bid . Amount . Add ( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
2020-02-28 22:16:22 +00:00
sdk . MaxInt (
2023-04-05 23:21:59 +00:00
sdkmath . NewInt ( 1 ) ,
2020-06-11 19:23:58 +00:00
sdk . NewDecFromInt ( auction . Bid . Amount ) . Mul ( k . GetParams ( ctx ) . IncrementSurplus ) . RoundInt ( ) ,
2020-02-28 22:16:22 +00:00
) ,
)
if bid . Amount . LT ( minNewBidAmt ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrBidTooSmall , "%s < %s%s" , bid , minNewBidAmt , auction . Bid . Denom )
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).
2022-01-08 00:39:27 +00:00
if ! bidder . Equals ( auction . Bidder ) && ! auction . Bid . IsZero ( ) { // bidder isn't same as before AND previous auction bid must exist
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . Bidder , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2019-12-12 00:16:10 +00:00
}
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , auction . Initiator , sdk . NewCoins ( bid . Sub ( auction . Bid ) ) )
2019-12-12 00:16:10 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2019-12-12 00:16:10 +00:00
}
2022-01-08 00:39:27 +00:00
// Received bid amount is burned from the module account
err = k . bankKeeper . BurnCoins ( ctx , auction . Initiator , sdk . NewCoins ( bid . Sub ( auction . Bid ) ) )
2019-12-12 00:16:10 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
// Update Auction
2020-06-11 19:23:58 +00:00
auction . Bidder = bidder
auction . Bid = bid
if ! auction . HasReceivedBids {
auction . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
auction . HasReceivedBids = true
2020-01-14 14:00:37 +00:00
}
2022-02-08 17:03:47 +00:00
auction . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . ForwardBidDuration ) , auction . MaxEndTime ) // increment timeout, up to MaxEndTime
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , auction . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBid , auction . Bid . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , auction . EndTime . Unix ( ) ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
2020-01-09 13:55:45 +00:00
2020-06-11 19:23:58 +00:00
return auction , nil
2020-01-09 13:55:45 +00:00
}
2020-01-09 17:25:16 +00:00
// PlaceForwardBidCollateral places a forward bid on a collateral auction, moving coins and returning the updated auction.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PlaceForwardBidCollateral ( ctx sdk . Context , auction * types . CollateralAuction , bidder sdk . AccAddress , bid sdk . Coin ) ( * types . CollateralAuction , error ) {
2020-01-09 15:43:42 +00:00
// Validate new bid
2020-06-11 19:23:58 +00:00
if bid . Denom != auction . Bid . Denom {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrInvalidBidDenom , "%s ≠ %s" , bid . Denom , auction . Bid . Denom )
2020-01-09 14:58:47 +00:00
}
2020-06-11 19:23:58 +00:00
if auction . IsReversePhase ( ) {
2022-02-08 17:03:47 +00:00
panic ( "cannot place reverse bid on auction in forward phase" )
2020-01-09 13:55:45 +00:00
}
2020-06-11 19:23:58 +00:00
minNewBidAmt := auction . Bid . Amount . Add ( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
2020-02-28 22:16:22 +00:00
sdk . MaxInt (
2023-04-05 23:21:59 +00:00
sdkmath . NewInt ( 1 ) ,
2020-06-11 19:23:58 +00:00
sdk . NewDecFromInt ( auction . Bid . Amount ) . Mul ( k . GetParams ( ctx ) . IncrementCollateral ) . RoundInt ( ) ,
2020-02-28 22:16:22 +00:00
) ,
)
2020-06-11 19:23:58 +00:00
minNewBidAmt = sdk . MinInt ( minNewBidAmt , auction . MaxBid . Amount ) // allow new bids to hit MaxBid even though it may be less than the increment %
2020-02-28 22:16:22 +00:00
if bid . Amount . LT ( minNewBidAmt ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrBidTooSmall , "%s < %s%s" , bid , minNewBidAmt , auction . Bid . Denom )
2019-12-12 00:16:10 +00:00
}
2020-06-11 19:23:58 +00:00
if auction . MaxBid . IsLT ( bid ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrBidTooLarge , "%s > %s" , bid , auction . 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).
2020-06-11 19:23:58 +00:00
if ! bidder . Equals ( auction . Bidder ) && ! auction . Bid . IsZero ( ) {
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . Bidder , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
}
2020-01-10 14:08:47 +00:00
// Increase in bid sent to auction initiator
2020-06-11 19:23:58 +00:00
bidIncrement := bid . Sub ( auction . Bid )
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , auction . Initiator , sdk . NewCoins ( bidIncrement ) )
2019-12-12 00:16:10 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2019-12-12 00:16:10 +00:00
}
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-06-11 19:23:58 +00:00
if auction . CorrespondingDebt . IsPositive ( ) {
2020-01-12 14:17:47 +00:00
2020-06-11 19:23:58 +00:00
debtAmountToReturn := sdk . MinInt ( bidIncrement . Amount , auction . CorrespondingDebt . Amount )
debtToReturn := sdk . NewCoin ( auction . CorrespondingDebt . Denom , debtAmountToReturn )
2020-01-12 14:17:47 +00:00
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , auction . Initiator , sdk . NewCoins ( debtToReturn ) )
2020-01-12 14:17:47 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-12 14:17:47 +00:00
}
2020-06-11 19:23:58 +00:00
auction . CorrespondingDebt = auction . CorrespondingDebt . Sub ( debtToReturn ) // debtToReturn will always be ≤ auction.CorrespondingDebt from the MinInt above
2020-01-12 14:17:47 +00:00
}
2019-12-12 00:16:10 +00:00
// Update Auction
2020-06-11 19:23:58 +00:00
auction . Bidder = bidder
auction . Bid = bid
if ! auction . HasReceivedBids {
auction . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
auction . HasReceivedBids = true
2020-01-14 14:00:37 +00:00
}
2022-02-08 17:03:47 +00:00
// If this forward bid converts this to a reverse, increase timeout with ReverseBidDuration
if auction . IsReversePhase ( ) {
auction . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . ReverseBidDuration ) , auction . MaxEndTime ) // increment timeout, up to MaxEndTime
} else {
auction . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . ForwardBidDuration ) , auction . MaxEndTime ) // increment timeout, up to MaxEndTime
}
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , auction . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyBid , auction . Bid . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , auction . EndTime . Unix ( ) ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
2019-12-12 00:16:10 +00:00
2020-06-11 19:23:58 +00:00
return auction , nil
2019-12-12 00:16:10 +00:00
}
2020-01-09 17:25:16 +00:00
// PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PlaceReverseBidCollateral ( ctx sdk . Context , auction * types . CollateralAuction , bidder sdk . AccAddress , lot sdk . Coin ) ( * types . CollateralAuction , error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2020-06-11 19:23:58 +00:00
if lot . Denom != auction . Lot . Denom {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrInvalidLotDenom , "%s ≠ %s" , lot . Denom , auction . Lot . Denom )
2020-01-09 14:58:47 +00:00
}
2020-06-11 19:23:58 +00:00
if ! auction . IsReversePhase ( ) {
2022-02-08 17:03:47 +00:00
panic ( "cannot place forward bid on auction in reverse phase" )
2019-12-12 00:16:10 +00:00
}
2020-06-11 19:23:58 +00:00
maxNewLotAmt := auction . Lot . Amount . Sub ( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
2020-02-28 22:16:22 +00:00
sdk . MaxInt (
2023-04-05 23:21:59 +00:00
sdkmath . NewInt ( 1 ) ,
2020-06-11 19:23:58 +00:00
sdk . NewDecFromInt ( auction . Lot . Amount ) . Mul ( k . GetParams ( ctx ) . IncrementCollateral ) . RoundInt ( ) ,
2020-02-28 22:16:22 +00:00
) ,
)
if lot . Amount . GT ( maxNewLotAmt ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrLotTooLarge , "%s > %s%s" , lot , maxNewLotAmt , auction . Lot . Denom )
2020-02-28 22:16:22 +00:00
}
if lot . IsNegative ( ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrLotTooSmall , "%s < 0%s" , lot , auction . Lot . Denom )
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
2020-06-11 19:23:58 +00:00
if ! bidder . Equals ( auction . Bidder ) {
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . Bidder , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2019-12-12 00:16:10 +00:00
}
2020-05-04 17:52:36 +00:00
2020-01-10 14:08:47 +00:00
// Decrease in lot is sent to weighted addresses (normally the CDP depositors)
2020-05-11 19:45:00 +00:00
// Note: splitting an integer amount across weighted buckets results in small errors.
2020-06-11 19:23:58 +00:00
lotPayouts , err := splitCoinIntoWeightedBuckets ( auction . Lot . Sub ( lot ) , auction . LotReturns . Weights )
2019-12-12 00:16:10 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2019-12-12 00:16:10 +00:00
}
2019-12-31 11:10:15 +00:00
for i , payout := range lotPayouts {
2020-05-08 15:07:11 +00:00
// if the payout amount is 0, don't send 0 coins
2020-05-11 20:07:39 +00:00
if ! payout . IsPositive ( ) {
2020-05-12 00:06:32 +00:00
continue
2020-05-11 20:07:39 +00:00
}
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . LotReturns . Addresses [ i ] , sdk . NewCoins ( payout ) )
2020-05-11 20:07:39 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2019-12-31 11:10:15 +00:00
}
}
2019-12-12 00:16:10 +00:00
// Update Auction
2020-06-11 19:23:58 +00:00
auction . Bidder = bidder
auction . Lot = lot
if ! auction . HasReceivedBids {
auction . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
auction . HasReceivedBids = true
2020-01-14 14:00:37 +00:00
}
2022-02-08 17:03:47 +00:00
auction . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . ReverseBidDuration ) , auction . MaxEndTime ) // increment timeout, up to MaxEndTime
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , auction . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyLot , auction . Lot . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , auction . EndTime . Unix ( ) ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
2019-12-12 00:16:10 +00:00
2020-06-11 19:23:58 +00:00
return auction , 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.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PlaceBidDebt ( ctx sdk . Context , auction * types . DebtAuction , bidder sdk . AccAddress , lot sdk . Coin ) ( * types . DebtAuction , error ) {
2020-01-10 14:08:47 +00:00
// Validate new bid
2020-06-11 19:23:58 +00:00
if lot . Denom != auction . Lot . Denom {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrInvalidLotDenom , "%s ≠ %s" , lot . Denom , auction . Lot . Denom )
2019-12-12 00:16:10 +00:00
}
2020-06-11 19:23:58 +00:00
maxNewLotAmt := auction . Lot . Amount . Sub ( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
2020-02-28 22:16:22 +00:00
sdk . MaxInt (
2023-04-05 23:21:59 +00:00
sdkmath . NewInt ( 1 ) ,
2020-06-11 19:23:58 +00:00
sdk . NewDecFromInt ( auction . Lot . Amount ) . Mul ( k . GetParams ( ctx ) . IncrementDebt ) . RoundInt ( ) ,
2020-02-28 22:16:22 +00:00
) ,
)
if lot . Amount . GT ( maxNewLotAmt ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrLotTooLarge , "%s > %s%s" , lot , maxNewLotAmt , auction . Lot . Denom )
2020-02-28 22:16:22 +00:00
}
if lot . IsNegative ( ) {
2023-04-05 23:21:59 +00:00
return auction , errorsmod . Wrapf ( types . ErrLotTooSmall , "%s ≤ %s%s" , lot , sdk . ZeroInt ( ) , auction . Lot . Denom )
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
2020-06-11 19:23:58 +00:00
if ! bidder . Equals ( auction . Bidder ) {
2022-01-08 00:39:27 +00:00
// Bidder sends coins to module
err := k . bankKeeper . SendCoinsFromAccountToModule ( ctx , bidder , types . ModuleName , sdk . NewCoins ( auction . Bid ) )
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2022-01-08 00:39:27 +00:00
// Coins are sent from module to old bidder
oldBidder := auction . Bidder
if oldBidder . Equals ( authtypes . NewModuleAddress ( auction . Initiator ) ) { // First bid on auction (where there is no previous bidder)
err = k . bankKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , auction . Initiator , sdk . NewCoins ( auction . Bid ) )
} else { // Second and later bids on auction (where previous bidder is a user account)
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , oldBidder , sdk . NewCoins ( auction . Bid ) )
}
2020-01-09 13:55:45 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-09 13:55:45 +00:00
}
2019-12-12 00:16:10 +00:00
}
2020-01-12 14:17:47 +00:00
2022-01-08 00:39:27 +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.
if auction . Bidder . Equals ( authtypes . NewModuleAddress ( auction . Initiator ) ) {
// Handle debt coins for first bid
2020-06-11 19:23:58 +00:00
debtAmountToReturn := sdk . MinInt ( auction . Bid . Amount , auction . CorrespondingDebt . Amount )
debtToReturn := sdk . NewCoin ( auction . CorrespondingDebt . Denom , debtAmountToReturn )
2020-01-12 14:17:47 +00:00
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , auction . Initiator , sdk . NewCoins ( debtToReturn ) )
2020-01-12 14:17:47 +00:00
if err != nil {
2020-06-11 19:23:58 +00:00
return auction , err
2020-01-12 14:17:47 +00:00
}
2020-06-11 19:23:58 +00:00
auction . CorrespondingDebt = auction . CorrespondingDebt . Sub ( debtToReturn ) // debtToReturn will always be ≤ auction.CorrespondingDebt from the MinInt above
2020-01-12 14:17:47 +00:00
}
2019-12-12 00:16:10 +00:00
// Update Auction
2020-06-11 19:23:58 +00:00
auction . Bidder = bidder
auction . Lot = lot
if ! auction . HasReceivedBids {
auction . MaxEndTime = ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . MaxAuctionDuration ) // set maximum ending time on receipt of first bid
auction . HasReceivedBids = true
2020-01-14 14:00:37 +00:00
}
2022-02-08 17:03:47 +00:00
auction . EndTime = earliestTime ( ctx . BlockTime ( ) . Add ( k . GetParams ( ctx ) . ForwardBidDuration ) , auction . MaxEndTime ) // increment timeout, up to MaxEndTime
2020-01-14 14:00:37 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeAuctionBid ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auction . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyBidder , auction . Bidder . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyLot , auction . Lot . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyEndTime , fmt . Sprintf ( "%d" , auction . EndTime . Unix ( ) ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
2019-12-12 00:16:10 +00:00
2020-06-11 19:23:58 +00:00
return auction , nil
2019-12-12 00:16:10 +00:00
}
// CloseAuction closes an auction and distributes funds to the highest bidder.
2020-04-23 16:35:58 +00:00
func ( k Keeper ) CloseAuction ( ctx sdk . Context , auctionID uint64 ) error {
2019-12-12 00:16:10 +00:00
auction , found := k . GetAuction ( ctx , auctionID )
if ! found {
2023-04-05 23:21:59 +00:00
return errorsmod . Wrapf ( types . ErrAuctionNotFound , "%d" , 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 ( ) ) {
2023-04-05 23:21:59 +00:00
return errorsmod . Wrapf ( types . ErrAuctionHasNotExpired , "block time %s, auction end time %s" , ctx . BlockTime ( ) . UTC ( ) , auction . GetEndTime ( ) . UTC ( ) )
2019-12-12 00:16:10 +00:00
}
// payout to the last bidder
2020-06-17 09:09:44 +00:00
var err error
2019-12-12 00:16:10 +00:00
switch auc := auction . ( type ) {
2022-01-08 00:39:27 +00:00
case * types . SurplusAuction :
2020-06-17 09:09:44 +00:00
err = k . PayoutSurplusAuction ( ctx , auc )
2022-01-08 00:39:27 +00:00
case * types . DebtAuction :
2020-06-17 09:09:44 +00:00
err = k . PayoutDebtAuction ( ctx , auc )
2022-01-08 00:39:27 +00:00
case * types . CollateralAuction :
2020-06-17 09:09:44 +00:00
err = k . PayoutCollateralAuction ( ctx , auc )
2019-12-12 00:16:10 +00:00
default :
2023-04-05 23:21:59 +00:00
err = errorsmod . Wrap ( types . ErrUnrecognizedAuctionType , auc . GetType ( ) )
2020-06-17 09:09:44 +00:00
}
if err != nil {
return err
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 ,
2020-06-11 19:23:58 +00:00
sdk . NewAttribute ( types . AttributeKeyAuctionID , fmt . Sprintf ( "%d" , auctionID ) ) ,
2020-05-11 13:55:32 +00:00
sdk . NewAttribute ( types . AttributeKeyCloseBlock , fmt . Sprintf ( "%d" , ctx . BlockHeight ( ) ) ) ,
2020-01-14 14:00:37 +00:00
) ,
)
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.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PayoutDebtAuction ( ctx sdk . Context , auction * types . DebtAuction ) error {
2020-05-04 17:52:36 +00:00
// create the coins that are needed to pay off the debt
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . MintCoins ( ctx , auction . Initiator , sdk . NewCoins ( auction . Lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
2020-05-11 13:55:32 +00:00
panic ( fmt . Errorf ( "could not mint coins: %w" , err ) )
2019-12-12 00:16:10 +00:00
}
2020-05-04 17:52:36 +00:00
// send the new coins from the initiator module to the bidder
2022-01-08 00:39:27 +00:00
err = k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , auction . Initiator , auction . Bidder , sdk . NewCoins ( auction . Lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return err
}
2020-05-04 17:52:36 +00:00
// if there is remaining debt, return it to the calling module to manage
2020-06-17 09:09:44 +00:00
if ! auction . CorrespondingDebt . IsPositive ( ) {
return nil
2020-01-12 14:17:47 +00:00
}
2020-06-17 09:09:44 +00:00
2022-01-08 00:39:27 +00:00
return k . bankKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , auction . Initiator , sdk . NewCoins ( auction . CorrespondingDebt ) )
2019-12-12 00:16:10 +00:00
}
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.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PayoutSurplusAuction ( ctx sdk . Context , auction * types . SurplusAuction ) error {
2020-05-04 17:52:36 +00:00
// Send the tokens from the auction module account where they are being managed to the bidder who won the auction
2022-01-08 00:39:27 +00:00
return k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . Bidder , sdk . NewCoins ( auction . Lot ) )
2020-01-09 15:43:42 +00:00
}
2020-01-09 17:25:16 +00:00
// PayoutCollateralAuction pays out the proceeds for a collateral auction.
2022-01-08 00:39:27 +00:00
func ( k Keeper ) PayoutCollateralAuction ( ctx sdk . Context , auction * types . CollateralAuction ) error {
2020-05-04 17:52:36 +00:00
// Send the tokens from the auction module account where they are being managed to the bidder who won the auction
2022-01-08 00:39:27 +00:00
err := k . bankKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleName , auction . Bidder , sdk . NewCoins ( auction . Lot ) )
2019-12-12 00:16:10 +00:00
if err != nil {
return err
}
2020-05-04 17:52:36 +00:00
// if there is remaining debt after the auction, send it back to the initiating module for management
2020-06-17 09:09:44 +00:00
if ! auction . CorrespondingDebt . IsPositive ( ) {
return nil
2020-01-12 14:17:47 +00:00
}
2020-06-17 09:09:44 +00:00
2022-01-08 00:39:27 +00:00
return k . bankKeeper . SendCoinsFromModuleToModule ( ctx , types . ModuleName , auction . Initiator , sdk . NewCoins ( auction . CorrespondingDebt ) )
2019-12-12 00:16:10 +00:00
}
2019-12-21 01:04:04 +00:00
2020-06-17 09:09:44 +00:00
// CloseExpiredAuctions iterates over all the auctions stored by until the current
// block timestamp and that are past (or at) their ending times and closes them,
// paying out to the highest bidder.
2020-04-23 16:35:58 +00:00
func ( k Keeper ) CloseExpiredAuctions ( ctx sdk . Context ) error {
2020-06-17 09:09:44 +00:00
var err error
k . IterateAuctionsByTime ( ctx , ctx . BlockTime ( ) , func ( id uint64 ) ( stop bool ) {
err = k . CloseAuction ( ctx , id )
if err != nil && ! errors . Is ( err , types . ErrAuctionNotFound ) {
// stop iteration
return true
}
// reset error in case the last element had an ErrAuctionNotFound
err = nil
2020-01-10 14:08:47 +00:00
return false
} )
2020-06-17 09:09:44 +00:00
return err
2020-01-10 14:08:47 +00:00
}
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
}
2020-01-29 14:42:03 +00:00
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.
2023-04-05 23:21:59 +00:00
func splitCoinIntoWeightedBuckets ( coin sdk . Coin , buckets [ ] sdkmath . Int ) ( [ ] sdk . Coin , error ) {
2019-12-31 11:10:15 +00:00
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
}