0g-chain/x/auction/keeper/auctions.go

592 lines
21 KiB
Go
Raw Normal View History

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"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
2019-12-12 00:16:10 +00:00
"github.com/cosmos/cosmos-sdk/x/supply"
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.
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,
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
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
auctionID, err := k.StoreNewAuction(ctx, auction)
2019-12-12 00:16:10 +00:00
if err != nil {
return 0, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeAuctionStart,
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
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()),
),
)
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.
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,
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.
macc := k.supplyKeeper.GetModuleAccount(ctx, buyer)
2020-01-09 13:55:45 +00:00
if !macc.HasPermission(supply.Minter) {
2020-05-04 17:52:36 +00:00
// TODO: this should panic?
return 0, sdkerrors.Wrap(types.ErrInvalidModulePermissions, supply.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
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
}
auctionID, err := k.StoreNewAuction(ctx, auction)
2019-12-12 00:16:10 +00:00
if err != nil {
return 0, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeAuctionStart,
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
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()),
),
)
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.
func (k Keeper) StartCollateralAuction(
ctx sdk.Context, seller string, lot, maxBid sdk.Coin,
lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin,
) (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,
types.DistantFuture,
2020-01-12 14:17:47 +00:00
maxBid,
weightedAddresses,
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
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
auctionID, err := k.StoreNewAuction(ctx, auction)
2019-12-12 00:16:10 +00:00
if err != nil {
return 0, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeAuctionStart,
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
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()),
),
)
2019-12-12 00:16:10 +00:00
return auctionID, nil
}
// PlaceBid places a bid on any auction.
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 {
return sdkerrors.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()) {
return sdkerrors.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
var (
err error
updatedAuction types.Auction
)
switch a := auction.(type) {
2020-01-09 16:09:19 +00:00
case types.SurplusAuction:
updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount)
2020-01-09 16:09:19 +00:00
case types.DebtAuction:
updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount)
2020-01-09 16:09:19 +00:00
case types.CollateralAuction:
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
default:
err = sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auction.GetType())
}
if err != nil {
return err
2019-12-12 00:16:10 +00:00
}
k.SetAuction(ctx, updatedAuction)
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.
func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, 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 {
return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s)", bid.Denom, a.Bid.Denom)
2019-12-12 00:16:10 +00:00
}
minNewBidAmt := a.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
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(k.GetParams(ctx).IncrementSurplus).RoundInt(),
),
)
if bid.Amount.LT(minNewBidAmt) {
return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.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, 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
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()),
2020-05-04 17:52:36 +00:00
sdk.NewAttribute(types.AttributeKeyBid, a.Bid.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.
func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, error) {
// Validate new bid
if bid.Denom != a.Bid.Denom {
return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s", bid.Denom, a.Bid.Denom)
}
2020-01-09 13:55:45 +00:00
if a.IsReversePhase() {
2020-05-04 17:52:36 +00:00
// TODO: panic maybe?
return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInReversePhase, "%d", a.ID)
2020-01-09 13:55:45 +00:00
}
minNewBidAmt := a.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
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(k.GetParams(ctx).IncrementCollateral).RoundInt(),
),
)
minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
if bid.Amount.LT(minNewBidAmt) {
return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.Denom)
2019-12-12 00:16:10 +00:00
}
2020-01-09 13:55:45 +00:00
if a.MaxBid.IsLT(bid) {
return a, sdkerrors.Wrapf(types.ErrBidTooLarge, "%s > %s", 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
}
// 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
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()),
2020-05-04 17:52:36 +00:00
sdk.NewAttribute(types.AttributeKeyBid, a.Bid.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.
func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, error) {
2020-01-10 14:08:47 +00:00
// Validate new bid
if lot.Denom != a.Lot.Denom {
return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom)
}
2020-01-09 13:55:45 +00:00
if !a.IsReversePhase() {
2020-05-04 17:52:36 +00:00
// TODO: Panic here?
return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInForwardPhase, "%d", a.ID)
2019-12-12 00:16:10 +00:00
}
maxNewLotAmt := a.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
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(k.GetParams(ctx).IncrementCollateral).RoundInt(),
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom)
}
if lot.IsNegative() {
return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.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
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-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-04 17:52:36 +00:00
// 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 {
2020-05-08 15:07:11 +00:00
// if the payout amount is 0, don't send 0 coins
2020-05-04 17:52:36 +00:00
if payout.IsPositive() {
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.LotReturns.Addresses[i], sdk.NewCoins(payout))
if err != nil {
return a, err
}
2019-12-31 11:10:15 +00:00
}
}
2019-12-12 00:16:10 +00:00
// Update Auction
a.Bidder = bidder
a.Lot = lot
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()),
2020-05-04 17:52:36 +00:00
sdk.NewAttribute(types.AttributeKeyLot, a.Lot.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.
func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, 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 {
return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom)
2019-12-12 00:16:10 +00:00
}
maxNewLotAmt := a.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
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(k.GetParams(ctx).IncrementDebt).RoundInt(),
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom)
}
if lot.IsNegative() {
return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.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
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
}
// 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
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()),
2020-05-04 17:52:36 +00:00
sdk.NewAttribute(types.AttributeKeyLot, a.Lot.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.
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 {
2020-05-04 17:52:36 +00:00
// TODO: panic if we are closing an auction in BB that isn't in the store?
// how else would we get here?
return sdkerrors.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()) {
2020-05-04 17:52:36 +00:00
// TODO: Do we check this upstream? should this be a panic
return sdkerrors.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
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 {
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:
return sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType())
2019-12-12 00:16:10 +00:00
}
k.DeleteAuction(ctx, auctionID)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeAuctionClose,
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
2020-05-08 15:07:11 +00:00
sdk.NewAttribute(types.AttributeKeyCloseBlock, fmt.Sprintf("%d", ctx.BlockHeight()))
),
)
2019-12-12 00:16:10 +00:00
return nil
}
2020-01-09 17:25:16 +00:00
// PayoutDebtAuction pays out the proceeds for a debt auction, first minting the coins.
func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) error {
2020-05-04 17:52:36 +00:00
// create the coins that are needed to pay off the debt
2019-12-12 00:16:10 +00:00
err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot))
if err != nil {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
2019-12-12 00:16:10 +00:00
return err
}
2020-05-04 17:52:36 +00:00
// send the new coins from the initiator module to the bidder
2019-12-12 00:16:10 +00:00
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, a.Initiator, a.Bidder, sdk.NewCoins(a.Lot))
if err != nil {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
2019-12-12 00:16:10 +00:00
return err
}
2020-05-04 17:52:36 +00:00
// if there is remaining debt, return it to the calling module to manage
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 {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
2020-01-12 14:17:47 +00:00
return err
}
}
2019-12-12 00:16:10 +00:00
return nil
}
2020-01-09 17:25:16 +00:00
// PayoutSurplusAuction pays out the proceeds for a surplus auction.
func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a 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
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
if err != nil {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
return err
}
return nil
}
2020-01-09 17:25:16 +00:00
// PayoutCollateralAuction pays out the proceeds for a collateral auction.
func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a 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
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
2019-12-12 00:16:10 +00:00
if err != nil {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
2019-12-12 00:16:10 +00:00
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-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 {
2020-05-04 17:52:36 +00:00
// TODO: how would we get here? should this be a panic?
2020-01-12 14:17:47 +00:00
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) error {
2020-01-10 14:08:47 +00:00
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
}
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.
func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, error) {
2019-12-31 11:10:15 +00:00
for _, bucket := range buckets {
if bucket.IsNegative() {
2020-05-04 17:52:36 +00:00
// TODO: Panic here? How would the code get here (should catch at validation)
return nil, fmt.Errorf("cannot split %s into bucket with negative weight (%s)", coin.String(), bucket.String())
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
}