Implement end blocker to process auctions
This commit is contained in:
parent
7681f88985
commit
667fd2f9eb
@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"cosmossdk.io/collections"
|
"cosmossdk.io/collections"
|
||||||
"cosmossdk.io/collections/indexes"
|
"cosmossdk.io/collections/indexes"
|
||||||
@ -20,6 +22,9 @@ import (
|
|||||||
auctiontypes "git.vdb.to/cerc-io/laconic2d/x/auction"
|
auctiontypes "git.vdb.to/cerc-io/laconic2d/x/auction"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CompletedAuctionDeleteTimeout => Completed auctions are deleted after this timeout (after reveals end time).
|
||||||
|
const CompletedAuctionDeleteTimeout = time.Hour * 24
|
||||||
|
|
||||||
type AuctionsIndexes struct {
|
type AuctionsIndexes struct {
|
||||||
Owner *indexes.Multi[string, string, auctiontypes.Auction]
|
Owner *indexes.Multi[string, string, auctiontypes.Auction]
|
||||||
}
|
}
|
||||||
@ -120,6 +125,23 @@ func (k Keeper) SaveAuction(ctx sdk.Context, auction *auctiontypes.Auction) erro
|
|||||||
// return nil
|
// return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAuction - deletes the auction.
|
||||||
|
func (k Keeper) DeleteAuction(ctx sdk.Context, auction auctiontypes.Auction) error {
|
||||||
|
// Delete all bids first.
|
||||||
|
bids, err := k.GetBids(ctx, auction.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bid := range bids {
|
||||||
|
if err := k.DeleteBid(ctx, *bid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.Auctions.Remove(ctx, auction.Id)
|
||||||
|
}
|
||||||
|
|
||||||
func (k Keeper) HasAuction(ctx sdk.Context, id string) (bool, error) {
|
func (k Keeper) HasAuction(ctx sdk.Context, id string) (bool, error) {
|
||||||
has, err := k.Auctions.Has(ctx, id)
|
has, err := k.Auctions.Has(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -191,6 +213,29 @@ func (k Keeper) ListAuctions(ctx sdk.Context) ([]auctiontypes.Auction, error) {
|
|||||||
return iter.Values()
|
return iter.Values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchAuctions - get all matching auctions.
|
||||||
|
func (k Keeper) MatchAuctions(ctx sdk.Context, matchFn func(*auctiontypes.Auction) (bool, error)) ([]*auctiontypes.Auction, error) {
|
||||||
|
var auctions []*auctiontypes.Auction
|
||||||
|
|
||||||
|
err := k.Auctions.Walk(ctx, nil, func(key string, value auctiontypes.Auction) (stop bool, err error) {
|
||||||
|
auctionMatched, err := matchFn(&value)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if auctionMatched {
|
||||||
|
auctions = append(auctions, &value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return auctions, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAuction - gets a record from the store.
|
// GetAuction - gets a record from the store.
|
||||||
func (k Keeper) GetAuctionById(ctx sdk.Context, id string) (auctiontypes.Auction, error) {
|
func (k Keeper) GetAuctionById(ctx sdk.Context, id string) (auctiontypes.Auction, error) {
|
||||||
auction, err := k.Auctions.Get(ctx, id)
|
auction, err := k.Auctions.Get(ctx, id)
|
||||||
@ -485,3 +530,210 @@ func (k Keeper) GetAuctionModuleBalances(ctx sdk.Context) sdk.Coins {
|
|||||||
|
|
||||||
return balances
|
return balances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k Keeper) EndBlockerProcessAuctions(ctx sdk.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Transition auction state (commit, reveal, expired, completed).
|
||||||
|
if err = k.processAuctionPhases(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete stale auctions.
|
||||||
|
return k.deleteCompletedAuctions(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) processAuctionPhases(ctx sdk.Context) error {
|
||||||
|
auctions, err := k.MatchAuctions(ctx, func(_ *auctiontypes.Auction) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, auction := range auctions {
|
||||||
|
// Commit -> Reveal state.
|
||||||
|
if auction.Status == auctiontypes.AuctionStatusCommitPhase && ctx.BlockTime().After(auction.CommitsEndTime) {
|
||||||
|
auction.Status = auctiontypes.AuctionStatusRevealPhase
|
||||||
|
if err = k.SaveAuction(ctx, auction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Moved auction %s to reveal phase.", auction.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reveal -> Expired state.
|
||||||
|
if auction.Status == auctiontypes.AuctionStatusRevealPhase && ctx.BlockTime().After(auction.RevealsEndTime) {
|
||||||
|
auction.Status = auctiontypes.AuctionStatusExpired
|
||||||
|
if err = k.SaveAuction(ctx, auction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Moved auction %s to expired state.", auction.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If auction has expired, pick a winner from revealed bids.
|
||||||
|
if auction.Status == auctiontypes.AuctionStatusExpired {
|
||||||
|
if err = k.pickAuctionWinner(ctx, auction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete completed stale auctions.
|
||||||
|
func (k Keeper) deleteCompletedAuctions(ctx sdk.Context) error {
|
||||||
|
auctions, err := k.MatchAuctions(ctx, func(auction *auctiontypes.Auction) (bool, error) {
|
||||||
|
deleteTime := auction.RevealsEndTime.Add(CompletedAuctionDeleteTimeout)
|
||||||
|
return auction.Status == auctiontypes.AuctionStatusCompleted && ctx.BlockTime().After(deleteTime), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, auction := range auctions {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Deleting completed auction %s after timeout.", auction.Id))
|
||||||
|
if err := k.DeleteAuction(ctx, *auction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Keeper) pickAuctionWinner(ctx sdk.Context, auction *auctiontypes.Auction) error {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Picking auction %s winner.", auction.Id))
|
||||||
|
|
||||||
|
var highestBid *auctiontypes.Bid
|
||||||
|
var secondHighestBid *auctiontypes.Bid
|
||||||
|
|
||||||
|
bids, err := k.GetBids(ctx, auction.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bid := range bids {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Processing bid %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
|
||||||
|
// Only consider revealed bids.
|
||||||
|
if bid.Status != auctiontypes.BidStatusRevealed {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Ignoring unrevealed bid %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init highest bid.
|
||||||
|
if highestBid == nil {
|
||||||
|
highestBid = bid
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Initializing 1st bid %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint: all
|
||||||
|
if highestBid.BidAmount.IsLT(bid.BidAmount) {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("New highest bid %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
|
||||||
|
secondHighestBid = highestBid
|
||||||
|
highestBid = bid
|
||||||
|
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Updated 1st bid %s %s", highestBid.BidderAddress, highestBid.BidAmount.String()))
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Updated 2nd bid %s %s", secondHighestBid.BidderAddress, secondHighestBid.BidAmount.String()))
|
||||||
|
|
||||||
|
} else if secondHighestBid == nil || secondHighestBid.BidAmount.IsLT(bid.BidAmount) {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("New 2nd highest bid %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
|
||||||
|
secondHighestBid = bid
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Updated 2nd bid %s %s", secondHighestBid.BidderAddress, secondHighestBid.BidAmount.String()))
|
||||||
|
} else {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Ignoring bid as it doesn't affect 1st/2nd price %s %s", bid.BidderAddress, bid.BidAmount.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highest bid is the winner, but pays second highest bid price.
|
||||||
|
auction.Status = auctiontypes.AuctionStatusCompleted
|
||||||
|
|
||||||
|
if highestBid != nil {
|
||||||
|
auction.WinnerAddress = highestBid.BidderAddress
|
||||||
|
auction.WinningBid = highestBid.BidAmount
|
||||||
|
|
||||||
|
// Winner pays 2nd price, if a 2nd price exists.
|
||||||
|
auction.WinningPrice = highestBid.BidAmount
|
||||||
|
if secondHighestBid != nil {
|
||||||
|
auction.WinningPrice = secondHighestBid.BidAmount
|
||||||
|
}
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Auction %s winner %s.", auction.Id, auction.WinnerAddress))
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Auction %s winner bid %s.", auction.Id, auction.WinningBid.String()))
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Auction %s winner price %s.", auction.Id, auction.WinningPrice.String()))
|
||||||
|
} else {
|
||||||
|
ctx.Logger().Info(fmt.Sprintf("Auction %s has no valid revealed bids (no winner).", auction.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := k.SaveAuction(ctx, auction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bid := range bids {
|
||||||
|
bidderAddress, err := sdk.AccAddressFromBech32(bid.BidderAddress)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Invalid bidderAddress address. %v", err))
|
||||||
|
panic("Invalid bidder address.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bid.Status == auctiontypes.BidStatusRevealed {
|
||||||
|
// Send reveal fee back to bidders that've revealed the bid.
|
||||||
|
sdkErr := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, auctiontypes.ModuleName, bidderAddress, sdk.NewCoins(bid.RevealFee))
|
||||||
|
if sdkErr != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Auction error returning reveal fee: %v", sdkErr))
|
||||||
|
panic(sdkErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send back locked bid amount to all bidders.
|
||||||
|
sdkErr := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, auctiontypes.ModuleName, bidderAddress, sdk.NewCoins(bid.BidAmount))
|
||||||
|
if sdkErr != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Auction error returning bid amount: %v", sdkErr))
|
||||||
|
panic(sdkErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process winner account (if nobody bids, there won't be a winner).
|
||||||
|
if auction.WinnerAddress != "" {
|
||||||
|
winnerAddress, err := sdk.AccAddressFromBech32(auction.WinnerAddress)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Invalid winner address. %v", err))
|
||||||
|
panic("Invalid winner address.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take 2nd price from winner.
|
||||||
|
sdkErr := k.bankKeeper.SendCoinsFromAccountToModule(ctx, winnerAddress, auctiontypes.ModuleName, sdk.NewCoins(auction.WinningPrice))
|
||||||
|
if sdkErr != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Auction error taking funds from winner: %v", sdkErr))
|
||||||
|
panic(sdkErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burn anything over the min. bid amount.
|
||||||
|
amountToBurn := auction.WinningPrice.Sub(auction.MinimumBid)
|
||||||
|
if amountToBurn.IsNegative() {
|
||||||
|
ctx.Logger().Error("Auction coins to burn cannot be negative.")
|
||||||
|
panic("Auction coins to burn cannot be negative.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use auction burn module account instead of actually burning coins to better keep track of supply.
|
||||||
|
sdkErr = k.bankKeeper.SendCoinsFromModuleToModule(ctx, auctiontypes.ModuleName, auctiontypes.AuctionBurnModuleAccountName, sdk.NewCoins(amountToBurn))
|
||||||
|
if sdkErr != nil {
|
||||||
|
ctx.Logger().Error(fmt.Sprintf("Auction error burning coins: %v", sdkErr))
|
||||||
|
panic(sdkErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Notify other modules (hook).
|
||||||
|
// ctx.Logger().Info(fmt.Sprintf("Auction %s notifying %d modules.", auction.Id, len(k.usageKeepers)))
|
||||||
|
// for _, keeper := range k.usageKeepers {
|
||||||
|
// ctx.Logger().Info(fmt.Sprintf("Auction %s notifying module %s.", auction.Id, keeper.ModuleName()))
|
||||||
|
// keeper.OnAuctionWinnerSelected(ctx, auction.Id)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -3,13 +3,14 @@ package module
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"git.vdb.to/cerc-io/laconic2d/x/auction/keeper"
|
"git.vdb.to/cerc-io/laconic2d/x/auction/keeper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EndBlocker is called every block
|
// EndBlocker is called every block
|
||||||
func EndBlocker(ctx context.Context, k keeper.Keeper) error {
|
func EndBlocker(ctx context.Context, k keeper.Keeper) error {
|
||||||
// TODO: Implement
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
// k.EndBlockerProcessAuctions(ctx)
|
|
||||||
|
|
||||||
return nil
|
return k.EndBlockerProcessAuctions(sdkCtx)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user