mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 09:47:28 +00:00 
			
		
		
		
	Various Auction TODOs (#281)
* make auctions not expire without bids * add events * improve genesis state validation * add genesis tests * update comment Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com> * add more events attributes Co-authored-by: Kevin Davis <karzak@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									aa6dfab6fd
								
							
						
					
					
						commit
						22e168d06a
					
				@ -261,7 +261,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
 | 
			
		||||
		slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
 | 
			
		||||
		staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
 | 
			
		||||
		validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
 | 
			
		||||
		auction.NewAppModule(app.auctionKeeper),
 | 
			
		||||
		auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
 | 
			
		||||
		cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper),
 | 
			
		||||
		pricefeed.NewAppModule(app.pricefeedKeeper),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ var (
 | 
			
		||||
	RegisterCodec        = types.RegisterCodec
 | 
			
		||||
	NewGenesisState      = types.NewGenesisState
 | 
			
		||||
	DefaultGenesisState  = types.DefaultGenesisState
 | 
			
		||||
	ValidateGenesis      = types.ValidateGenesis
 | 
			
		||||
	GetAuctionKey        = types.GetAuctionKey
 | 
			
		||||
	GetAuctionByTimeKey  = types.GetAuctionByTimeKey
 | 
			
		||||
	Uint64FromBytes      = types.Uint64FromBytes
 | 
			
		||||
@ -58,7 +57,8 @@ type (
 | 
			
		||||
	CollateralAuction = types.CollateralAuction
 | 
			
		||||
	WeightedAddresses = types.WeightedAddresses
 | 
			
		||||
	SupplyKeeper      = types.SupplyKeeper
 | 
			
		||||
	Auctions          = types.Auctions
 | 
			
		||||
	GenesisAuctions   = types.GenesisAuctions
 | 
			
		||||
	GenesisAuction    = types.GenesisAuction
 | 
			
		||||
	GenesisState      = types.GenesisState
 | 
			
		||||
	MsgPlaceBid       = types.MsgPlaceBid
 | 
			
		||||
	Params            = types.Params
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,39 @@
 | 
			
		||||
package auction
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// InitGenesis initializes the store state from genesis data.
 | 
			
		||||
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
 | 
			
		||||
	keeper.SetNextAuctionID(ctx, data.NextAuctionID)
 | 
			
		||||
// InitGenesis initializes the store state from a genesis state.
 | 
			
		||||
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper, gs GenesisState) {
 | 
			
		||||
	if err := gs.Validate(); err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keeper.SetParams(ctx, data.Params)
 | 
			
		||||
	keeper.SetNextAuctionID(ctx, gs.NextAuctionID)
 | 
			
		||||
 | 
			
		||||
	for _, a := range data.Auctions {
 | 
			
		||||
	keeper.SetParams(ctx, gs.Params)
 | 
			
		||||
 | 
			
		||||
	totalAuctionCoins := sdk.NewCoins()
 | 
			
		||||
	for _, a := range gs.Auctions {
 | 
			
		||||
		keeper.SetAuction(ctx, a)
 | 
			
		||||
		// find the total coins that should be present in the module account
 | 
			
		||||
		totalAuctionCoins.Add(a.GetModuleAccountCoins())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check if the module account exists
 | 
			
		||||
	moduleAcc := supplyKeeper.GetModuleAccount(ctx, ModuleName)
 | 
			
		||||
	if moduleAcc == nil {
 | 
			
		||||
		panic(fmt.Sprintf("%s module account has not been set", ModuleName))
 | 
			
		||||
	}
 | 
			
		||||
	// check module coins match auction coins
 | 
			
		||||
	// Note: Other sdk modules do not check this, instead just using the existing module account coins, or if zero, setting them.
 | 
			
		||||
	if !moduleAcc.GetCoins().IsEqual(totalAuctionCoins) {
 | 
			
		||||
		panic(fmt.Sprintf("total auction coins (%s) do not equal (%s) module account (%s) ", moduleAcc.GetCoins(), ModuleName, totalAuctionCoins))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,9 +46,13 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
 | 
			
		||||
 | 
			
		||||
	params := keeper.GetParams(ctx)
 | 
			
		||||
 | 
			
		||||
	var genAuctions Auctions
 | 
			
		||||
	genAuctions := GenesisAuctions{} // return empty list instead of nil if no auctions
 | 
			
		||||
	keeper.IterateAuctions(ctx, func(a Auction) bool {
 | 
			
		||||
		genAuctions = append(genAuctions, a)
 | 
			
		||||
		ga, ok := a.(types.GenesisAuction)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			panic("could not convert stored auction to GenesisAuction type")
 | 
			
		||||
		}
 | 
			
		||||
		genAuctions = append(genAuctions, ga)
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										109
									
								
								x/auction/genesis_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								x/auction/genesis_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
			
		||||
package auction_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	abci "github.com/tendermint/tendermint/abci/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/app"
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var testTime = time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
			
		||||
var testAuction = auction.NewCollateralAuction(
 | 
			
		||||
	"seller",
 | 
			
		||||
	c("lotdenom", 10),
 | 
			
		||||
	testTime,
 | 
			
		||||
	c("biddenom", 1000),
 | 
			
		||||
	auction.WeightedAddresses{},
 | 
			
		||||
	c("debt", 1000),
 | 
			
		||||
).WithID(3).(auction.GenesisAuction)
 | 
			
		||||
 | 
			
		||||
func TestInitGenesis(t *testing.T) {
 | 
			
		||||
	t.Run("valid", func(t *testing.T) {
 | 
			
		||||
		// setup keepers
 | 
			
		||||
		tApp := app.NewTestApp()
 | 
			
		||||
		keeper := tApp.GetAuctionKeeper()
 | 
			
		||||
		ctx := tApp.NewContext(true, abci.Header{})
 | 
			
		||||
		// create genesis
 | 
			
		||||
		gs := auction.NewGenesisState(
 | 
			
		||||
			10,
 | 
			
		||||
			auction.DefaultParams(),
 | 
			
		||||
			auction.GenesisAuctions{testAuction},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		// run init
 | 
			
		||||
		require.NotPanics(t, func() {
 | 
			
		||||
			auction.InitGenesis(ctx, keeper, tApp.GetSupplyKeeper(), gs)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// check state is as expected
 | 
			
		||||
		actualID, err := keeper.GetNextAuctionID(ctx)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		require.Equal(t, gs.NextAuctionID, actualID)
 | 
			
		||||
 | 
			
		||||
		require.Equal(t, gs.Params, keeper.GetParams(ctx))
 | 
			
		||||
 | 
			
		||||
		// TODO is there a nicer way of comparing state?
 | 
			
		||||
		sort.Slice(gs.Auctions, func(i, j int) bool {
 | 
			
		||||
			return gs.Auctions[i].GetID() > gs.Auctions[j].GetID()
 | 
			
		||||
		})
 | 
			
		||||
		i := 0
 | 
			
		||||
		keeper.IterateAuctions(ctx, func(a auction.Auction) bool {
 | 
			
		||||
			require.Equal(t, gs.Auctions[i], a)
 | 
			
		||||
			i++
 | 
			
		||||
			return false
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
	t.Run("invalid", func(t *testing.T) {
 | 
			
		||||
		// setup keepers
 | 
			
		||||
		tApp := app.NewTestApp()
 | 
			
		||||
		ctx := tApp.NewContext(true, abci.Header{})
 | 
			
		||||
 | 
			
		||||
		// create invalid genesis
 | 
			
		||||
		gs := auction.NewGenesisState(
 | 
			
		||||
			0, // next id < testAuction ID
 | 
			
		||||
			auction.DefaultParams(),
 | 
			
		||||
			auction.GenesisAuctions{testAuction},
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		// check init fails
 | 
			
		||||
		require.Panics(t, func() {
 | 
			
		||||
			auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs)
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportGenesis(t *testing.T) {
 | 
			
		||||
	t.Run("default", func(t *testing.T) {
 | 
			
		||||
		// setup state
 | 
			
		||||
		tApp := app.NewTestApp()
 | 
			
		||||
		ctx := tApp.NewContext(true, abci.Header{})
 | 
			
		||||
		tApp.InitializeFromGenesisStates()
 | 
			
		||||
 | 
			
		||||
		// export
 | 
			
		||||
		gs := auction.ExportGenesis(ctx, tApp.GetAuctionKeeper())
 | 
			
		||||
 | 
			
		||||
		// check state matches
 | 
			
		||||
		require.Equal(t, auction.DefaultGenesisState(), gs)
 | 
			
		||||
	})
 | 
			
		||||
	t.Run("one auction", func(t *testing.T) {
 | 
			
		||||
		// setup state
 | 
			
		||||
		tApp := app.NewTestApp()
 | 
			
		||||
		ctx := tApp.NewContext(true, abci.Header{})
 | 
			
		||||
		tApp.InitializeFromGenesisStates()
 | 
			
		||||
		tApp.GetAuctionKeeper().SetAuction(ctx, testAuction)
 | 
			
		||||
 | 
			
		||||
		// export
 | 
			
		||||
		gs := auction.ExportGenesis(ctx, tApp.GetAuctionKeeper())
 | 
			
		||||
 | 
			
		||||
		// check state matches
 | 
			
		||||
		expectedGenesisState := auction.DefaultGenesisState()
 | 
			
		||||
		expectedGenesisState.Auctions = append(expectedGenesisState.Auctions, testAuction)
 | 
			
		||||
		require.Equal(t, expectedGenesisState, gs)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@ -4,11 +4,15 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewHandler returns a function to handle all "auction" type messages.
 | 
			
		||||
func NewHandler(keeper Keeper) sdk.Handler {
 | 
			
		||||
	return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
 | 
			
		||||
		ctx = ctx.WithEventManager(sdk.NewEventManager())
 | 
			
		||||
 | 
			
		||||
		switch msg := msg.(type) {
 | 
			
		||||
		case MsgPlaceBid:
 | 
			
		||||
			return handleMsgPlaceBid(ctx, keeper, msg)
 | 
			
		||||
@ -26,5 +30,15 @@ func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Resu
 | 
			
		||||
		return err.Result()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sdk.Result{}
 | 
			
		||||
	ctx.EventManager().EmitEvent(
 | 
			
		||||
		sdk.NewEvent(
 | 
			
		||||
			sdk.EventTypeMessage,
 | 
			
		||||
			sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
 | 
			
		||||
			sdk.NewAttribute(sdk.AttributeKeySender, msg.Bidder.String()),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return sdk.Result{
 | 
			
		||||
		Events: ctx.EventManager().Events(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin
 | 
			
		||||
		seller,
 | 
			
		||||
		lot,
 | 
			
		||||
		bidDenom,
 | 
			
		||||
		ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration))
 | 
			
		||||
		types.DistantFuture)
 | 
			
		||||
 | 
			
		||||
	err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -27,6 +27,16 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin
 | 
			
		||||
	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.Name()),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	return auctionID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -37,7 +47,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
 | 
			
		||||
		buyer,
 | 
			
		||||
		bid,
 | 
			
		||||
		initialLot,
 | 
			
		||||
		ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration),
 | 
			
		||||
		types.DistantFuture,
 | 
			
		||||
		debt)
 | 
			
		||||
 | 
			
		||||
	// This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker.
 | 
			
		||||
@ -55,6 +65,16 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
 | 
			
		||||
	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.Name()),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	return auctionID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -68,7 +88,7 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
 | 
			
		||||
	auction := types.NewCollateralAuction(
 | 
			
		||||
		seller,
 | 
			
		||||
		lot,
 | 
			
		||||
		ctx.BlockTime().Add(types.DefaultMaxAuctionDuration),
 | 
			
		||||
		types.DistantFuture,
 | 
			
		||||
		maxBid,
 | 
			
		||||
		weightedAddresses,
 | 
			
		||||
		debt)
 | 
			
		||||
@ -86,6 +106,16 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
 | 
			
		||||
	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.Name()),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	return auctionID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -128,6 +158,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k.SetAuction(ctx, updatedAuction)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -142,7 +173,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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).
 | 
			
		||||
	// Catch edge cases of a bidder replacing their own bid, or the amount being zero (sending zero coins produces meaningless send events).
 | 
			
		||||
	if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() {
 | 
			
		||||
		err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -166,7 +197,21 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
 | 
			
		||||
	// Update Auction
 | 
			
		||||
	a.Bidder = bidder
 | 
			
		||||
	a.Bid = bid
 | 
			
		||||
	a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout
 | 
			
		||||
	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())),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
@ -216,13 +261,26 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
 | 
			
		||||
			return a, err
 | 
			
		||||
		}
 | 
			
		||||
		a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
 | 
			
		||||
		// TODO optionally burn out debt and stable just returned to liquidator
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update Auction
 | 
			
		||||
	a.Bidder = bidder
 | 
			
		||||
	a.Bid = bid
 | 
			
		||||
	a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout
 | 
			
		||||
	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())),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
@ -271,7 +329,21 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc
 | 
			
		||||
	// Update Auction
 | 
			
		||||
	a.Bidder = bidder
 | 
			
		||||
	a.Lot = lot
 | 
			
		||||
	a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout
 | 
			
		||||
	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())),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
@ -312,13 +384,26 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
 | 
			
		||||
			return a, err
 | 
			
		||||
		}
 | 
			
		||||
		a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
 | 
			
		||||
		// TODO optionally burn out debt and stable just returned to liquidator
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update Auction
 | 
			
		||||
	a.Bidder = bidder
 | 
			
		||||
	a.Lot = lot
 | 
			
		||||
	a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout
 | 
			
		||||
	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())),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return a, nil
 | 
			
		||||
}
 | 
			
		||||
@ -354,6 +439,13 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k.DeleteAuction(ctx, auctionID)
 | 
			
		||||
 | 
			
		||||
	ctx.EventManager().EmitEvent(
 | 
			
		||||
		sdk.NewEvent(
 | 
			
		||||
			types.EventTypeAuctionClose,
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -227,13 +227,14 @@ func TestStartSurplusAuction(t *testing.T) {
 | 
			
		||||
				// check auction in store and is correct
 | 
			
		||||
				require.True(t, found)
 | 
			
		||||
				expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{
 | 
			
		||||
					ID:         0,
 | 
			
		||||
					Initiator:  tc.args.seller,
 | 
			
		||||
					Lot:        tc.args.lot,
 | 
			
		||||
					Bidder:     nil,
 | 
			
		||||
					Bid:        c(tc.args.bidDenom, 0),
 | 
			
		||||
					EndTime:    tc.blockTime.Add(types.DefaultMaxAuctionDuration),
 | 
			
		||||
					MaxEndTime: tc.blockTime.Add(types.DefaultMaxAuctionDuration),
 | 
			
		||||
					ID:              0,
 | 
			
		||||
					Initiator:       tc.args.seller,
 | 
			
		||||
					Lot:             tc.args.lot,
 | 
			
		||||
					Bidder:          nil,
 | 
			
		||||
					Bid:             c(tc.args.bidDenom, 0),
 | 
			
		||||
					HasReceivedBids: false,
 | 
			
		||||
					EndTime:         types.DistantFuture,
 | 
			
		||||
					MaxEndTime:      types.DistantFuture,
 | 
			
		||||
				}})
 | 
			
		||||
				require.Equal(t, expectedAuction, actualAuc)
 | 
			
		||||
			} else {
 | 
			
		||||
 | 
			
		||||
@ -2,18 +2,19 @@ package auction
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/client/context"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/codec"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/types/module"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	abci "github.com/tendermint/tendermint/abci/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction/client/cli"
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction/client/rest"
 | 
			
		||||
	"github.com/kava-labs/kava/x/auction/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@ -41,12 +42,12 @@ func (AppModuleBasic) DefaultGenesis() json.RawMessage {
 | 
			
		||||
 | 
			
		||||
// ValidateGenesis performs genesis state validation for the auction module.
 | 
			
		||||
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
 | 
			
		||||
	var data GenesisState
 | 
			
		||||
	err := ModuleCdc.UnmarshalJSON(bz, &data)
 | 
			
		||||
	var gs GenesisState
 | 
			
		||||
	err := ModuleCdc.UnmarshalJSON(bz, &gs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err)
 | 
			
		||||
	}
 | 
			
		||||
	return ValidateGenesis(data)
 | 
			
		||||
	return gs.Validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterRESTRoutes registers the REST routes for the auction module.
 | 
			
		||||
@ -67,14 +68,17 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
// AppModule implements the sdk.AppModule interface.
 | 
			
		||||
type AppModule struct {
 | 
			
		||||
	AppModuleBasic
 | 
			
		||||
	keeper Keeper
 | 
			
		||||
 | 
			
		||||
	keeper       Keeper
 | 
			
		||||
	supplyKeeper types.SupplyKeeper
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAppModule creates a new AppModule object
 | 
			
		||||
func NewAppModule(keeper Keeper) AppModule {
 | 
			
		||||
func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule {
 | 
			
		||||
	return AppModule{
 | 
			
		||||
		AppModuleBasic: AppModuleBasic{},
 | 
			
		||||
		keeper:         keeper,
 | 
			
		||||
		supplyKeeper:   supplyKeeper,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -106,7 +110,7 @@ func (am AppModule) NewQuerierHandler() sdk.Querier {
 | 
			
		||||
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
 | 
			
		||||
	var genesisState GenesisState
 | 
			
		||||
	ModuleCdc.MustUnmarshalJSON(data, &genesisState)
 | 
			
		||||
	InitGenesis(ctx, am.keeper, genesisState)
 | 
			
		||||
	InitGenesis(ctx, am.keeper, am.supplyKeeper, genesisState)
 | 
			
		||||
	return []abci.ValidatorUpdate{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,32 @@
 | 
			
		||||
# Events
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
TODO: Add events for auction_start, auction_end, auction_bid
 | 
			
		||||
 -->
 | 
			
		||||
The `x/auction` module emits the following events:
 | 
			
		||||
 | 
			
		||||
## Triggered By Other Modules
 | 
			
		||||
 | 
			
		||||
| Type          | Attribute Key | Attribute Value     |
 | 
			
		||||
| ------------- | ------------- | ------------------- |
 | 
			
		||||
| auction_start | auction_id    | {auction ID}        |
 | 
			
		||||
| auction_start | auction_type  | {auction type}      |
 | 
			
		||||
| auction_start | lot_denom     | {auction lot denom} |
 | 
			
		||||
| auction_start | bid_denom     | {auction bid denom} |
 | 
			
		||||
 | 
			
		||||
## Handlers
 | 
			
		||||
 | 
			
		||||
### MsgPlaceBid
 | 
			
		||||
 | 
			
		||||
| Type        | Attribute Key | Attribute Value    |
 | 
			
		||||
| ----------- | ------------- | ------------------ |
 | 
			
		||||
| auction_bid | auction_id    | {auction ID}       |
 | 
			
		||||
| auction_bid | bidder        | {latest bidder}    |
 | 
			
		||||
| auction_bid | bid_amount    | {coin amount}      |
 | 
			
		||||
| auction_bid | lot_amount    | {coin amount}      |
 | 
			
		||||
| auction_bid | end_time      | {auction end time} |
 | 
			
		||||
| message     | module        | auction            |
 | 
			
		||||
| message     | sender        | {sender address}   |
 | 
			
		||||
 | 
			
		||||
## EndBlock
 | 
			
		||||
 | 
			
		||||
| Type          | Attribute Key | Attribute Value |
 | 
			
		||||
| ------------- | ------------- | --------------- |
 | 
			
		||||
| auction_close | auction_id    | {auction ID}    |
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,11 @@ import (
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/x/supply"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// distantFuture is a very large time value to use as initial the ending time for auctions.
 | 
			
		||||
// It is not set to the max time supported. This can cause problems with time comparisons, see https://stackoverflow.com/a/32620397.
 | 
			
		||||
// Also amino panics when encoding times ≥ the start of year 10000.
 | 
			
		||||
var DistantFuture time.Time = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
			
		||||
 | 
			
		||||
// Auction is an interface for handling common actions on auctions.
 | 
			
		||||
type Auction interface {
 | 
			
		||||
	GetID() uint64
 | 
			
		||||
@ -17,13 +22,14 @@ type Auction interface {
 | 
			
		||||
 | 
			
		||||
// BaseAuction is a common type shared by all Auctions.
 | 
			
		||||
type BaseAuction struct {
 | 
			
		||||
	ID         uint64
 | 
			
		||||
	Initiator  string         // Module name that starts the auction. Pays out Lot.
 | 
			
		||||
	Lot        sdk.Coin       // Coins that will paid out by Initiator to the winning bidder.
 | 
			
		||||
	Bidder     sdk.AccAddress // Latest bidder. Receiver of Lot.
 | 
			
		||||
	Bid        sdk.Coin       // Coins paid into the auction the bidder.
 | 
			
		||||
	EndTime    time.Time      // Current auction closing time. Triggers at the end of the block with time ≥ EndTime.
 | 
			
		||||
	MaxEndTime time.Time      // Maximum closing time. Auctions can close before this but never after.
 | 
			
		||||
	ID              uint64
 | 
			
		||||
	Initiator       string         // Module name that starts the auction. Pays out Lot.
 | 
			
		||||
	Lot             sdk.Coin       // Coins that will paid out by Initiator to the winning bidder.
 | 
			
		||||
	Bidder          sdk.AccAddress // Latest bidder. Receiver of Lot.
 | 
			
		||||
	Bid             sdk.Coin       // Coins paid into the auction the bidder.
 | 
			
		||||
	HasReceivedBids bool           // Whether the auction has received any bids or not.
 | 
			
		||||
	EndTime         time.Time      // Current auction closing time. Triggers at the end of the block with time ≥ EndTime.
 | 
			
		||||
	MaxEndTime      time.Time      // Maximum closing time. Auctions can close before this but never after.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetID is a getter for auction ID.
 | 
			
		||||
@ -32,6 +38,13 @@ func (a BaseAuction) GetID() uint64 { return a.ID }
 | 
			
		||||
// GetEndTime is a getter for auction end time.
 | 
			
		||||
func (a BaseAuction) GetEndTime() time.Time { return a.EndTime }
 | 
			
		||||
 | 
			
		||||
func (a BaseAuction) Validate() error {
 | 
			
		||||
	if a.EndTime.After(a.MaxEndTime) {
 | 
			
		||||
		return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a BaseAuction) String() string {
 | 
			
		||||
	return fmt.Sprintf(`Auction %d:
 | 
			
		||||
  Initiator:              %s
 | 
			
		||||
@ -55,16 +68,27 @@ type SurplusAuction struct {
 | 
			
		||||
// WithID returns an auction with the ID set.
 | 
			
		||||
func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a }
 | 
			
		||||
 | 
			
		||||
// Name returns a name for this auction type. Used to identify auctions in event attributes.
 | 
			
		||||
func (a SurplusAuction) Name() string { return "surplus" }
 | 
			
		||||
 | 
			
		||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
 | 
			
		||||
// It is used in genesis initialize the module account correctly.
 | 
			
		||||
func (a SurplusAuction) GetModuleAccountCoins() sdk.Coins {
 | 
			
		||||
	// a.Bid is paid out on bids, so is never stored in the module account
 | 
			
		||||
	return sdk.NewCoins(a.Lot)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSurplusAuction returns a new surplus auction.
 | 
			
		||||
func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction {
 | 
			
		||||
	auction := SurplusAuction{BaseAuction{
 | 
			
		||||
		// no ID
 | 
			
		||||
		Initiator:  seller,
 | 
			
		||||
		Lot:        lot,
 | 
			
		||||
		Bidder:     nil,
 | 
			
		||||
		Bid:        sdk.NewInt64Coin(bidDenom, 0),
 | 
			
		||||
		EndTime:    endTime,
 | 
			
		||||
		MaxEndTime: endTime,
 | 
			
		||||
		Initiator:       seller,
 | 
			
		||||
		Lot:             lot,
 | 
			
		||||
		Bidder:          nil,
 | 
			
		||||
		Bid:             sdk.NewInt64Coin(bidDenom, 0),
 | 
			
		||||
		HasReceivedBids: false, // new auctions don't have any bids
 | 
			
		||||
		EndTime:         endTime,
 | 
			
		||||
		MaxEndTime:      endTime,
 | 
			
		||||
	}}
 | 
			
		||||
	return auction
 | 
			
		||||
}
 | 
			
		||||
@ -79,20 +103,32 @@ type DebtAuction struct {
 | 
			
		||||
// WithID returns an auction with the ID set.
 | 
			
		||||
func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a }
 | 
			
		||||
 | 
			
		||||
// Name returns a name for this auction type. Used to identify auctions in event attributes.
 | 
			
		||||
func (a DebtAuction) Name() string { return "debt" }
 | 
			
		||||
 | 
			
		||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
 | 
			
		||||
// It is used in genesis initialize the module account correctly.
 | 
			
		||||
func (a DebtAuction) GetModuleAccountCoins() sdk.Coins {
 | 
			
		||||
	// a.Lot is minted at auction close, so is never stored in the module account
 | 
			
		||||
	// a.Bid is paid out on bids, so is never stored in the module account
 | 
			
		||||
	return sdk.NewCoins(a.CorrespondingDebt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebtAuction returns a new debt auction.
 | 
			
		||||
func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time, debt sdk.Coin) DebtAuction {
 | 
			
		||||
func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction {
 | 
			
		||||
	// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
 | 
			
		||||
	// Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount.
 | 
			
		||||
	// This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed.
 | 
			
		||||
	auction := DebtAuction{
 | 
			
		||||
		BaseAuction: BaseAuction{
 | 
			
		||||
			// no ID
 | 
			
		||||
			Initiator:  buyerModAccName,
 | 
			
		||||
			Lot:        initialLot,
 | 
			
		||||
			Bidder:     supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer.
 | 
			
		||||
			Bid:        bid,                                      // amount that the buyer is buying - doesn't change over course of auction
 | 
			
		||||
			EndTime:    EndTime,
 | 
			
		||||
			MaxEndTime: EndTime},
 | 
			
		||||
			Initiator:       buyerModAccName,
 | 
			
		||||
			Lot:             initialLot,
 | 
			
		||||
			Bidder:          supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer.
 | 
			
		||||
			Bid:             bid,                                      // amount that the buyer is buying - doesn't change over course of auction
 | 
			
		||||
			HasReceivedBids: false,                                    // new auctions don't have any bids
 | 
			
		||||
			EndTime:         endTime,
 | 
			
		||||
			MaxEndTime:      endTime},
 | 
			
		||||
		CorrespondingDebt: debt,
 | 
			
		||||
	}
 | 
			
		||||
	return auction
 | 
			
		||||
@ -113,6 +149,16 @@ type CollateralAuction struct {
 | 
			
		||||
// WithID returns an auction with the ID set.
 | 
			
		||||
func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a }
 | 
			
		||||
 | 
			
		||||
// Name returns a name for this auction type. Used to identify auctions in event attributes.
 | 
			
		||||
func (a CollateralAuction) Name() string { return "collateral" }
 | 
			
		||||
 | 
			
		||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
 | 
			
		||||
// It is used in genesis initialize the module account correctly.
 | 
			
		||||
func (a CollateralAuction) GetModuleAccountCoins() sdk.Coins {
 | 
			
		||||
	// a.Bid is paid out on bids, so is never stored in the module account
 | 
			
		||||
	return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsReversePhase returns whether the auction has switched over to reverse phase or not.
 | 
			
		||||
// Auction initially start in forward phase.
 | 
			
		||||
func (a CollateralAuction) IsReversePhase() bool {
 | 
			
		||||
@ -136,16 +182,17 @@ func (a CollateralAuction) String() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCollateralAuction returns a new collateral auction.
 | 
			
		||||
func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction {
 | 
			
		||||
func NewCollateralAuction(seller string, lot sdk.Coin, endTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction {
 | 
			
		||||
	auction := CollateralAuction{
 | 
			
		||||
		BaseAuction: BaseAuction{
 | 
			
		||||
			// no ID
 | 
			
		||||
			Initiator:  seller,
 | 
			
		||||
			Lot:        lot,
 | 
			
		||||
			Bidder:     nil,
 | 
			
		||||
			Bid:        sdk.NewInt64Coin(maxBid.Denom, 0),
 | 
			
		||||
			EndTime:    EndTime,
 | 
			
		||||
			MaxEndTime: EndTime},
 | 
			
		||||
			Initiator:       seller,
 | 
			
		||||
			Lot:             lot,
 | 
			
		||||
			Bidder:          nil,
 | 
			
		||||
			Bid:             sdk.NewInt64Coin(maxBid.Denom, 0),
 | 
			
		||||
			HasReceivedBids: false, // new auctions don't have any bids
 | 
			
		||||
			EndTime:         endTime,
 | 
			
		||||
			MaxEndTime:      endTime},
 | 
			
		||||
		CorrespondingDebt: debt,
 | 
			
		||||
		MaxBid:            maxBid,
 | 
			
		||||
		LotReturns:        lotReturns,
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ func init() {
 | 
			
		||||
func RegisterCodec(cdc *codec.Codec) {
 | 
			
		||||
	cdc.RegisterConcrete(MsgPlaceBid{}, "auction/MsgPlaceBid", nil)
 | 
			
		||||
 | 
			
		||||
	cdc.RegisterInterface((*GenesisAuction)(nil), nil)
 | 
			
		||||
	cdc.RegisterInterface((*Auction)(nil), nil)
 | 
			
		||||
	cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil)
 | 
			
		||||
	cdc.RegisterConcrete(DebtAuction{}, "auction/DebtAuction", nil)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								x/auction/types/events.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								x/auction/types/events.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	EventTypeAuctionStart = "auction_start"
 | 
			
		||||
	EventTypeAuctionBid   = "auction_bid"
 | 
			
		||||
	EventTypeAuctionClose = "auction_close"
 | 
			
		||||
 | 
			
		||||
	AttributeValueCategory  = ModuleName
 | 
			
		||||
	AttributeKeyAuctionID   = "auction_id"
 | 
			
		||||
	AttributeKeyAuctionType = "auction_type"
 | 
			
		||||
	AttributeKeyBidder      = "bidder"
 | 
			
		||||
	AttributeKeyBidDenom    = "bid_denom"
 | 
			
		||||
	AttributeKeyLotDenom    = "lot_denom"
 | 
			
		||||
	AttributeKeyBidAmount   = "bid_amount"
 | 
			
		||||
	AttributeKeyLotAmount   = "lot_amount"
 | 
			
		||||
	AttributeKeyEndTime     = "end_time"
 | 
			
		||||
)
 | 
			
		||||
@ -2,20 +2,30 @@ package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Auctions is a slice of auctions.
 | 
			
		||||
type Auctions []Auction
 | 
			
		||||
// GenesisAuction is an interface that extends the auction interface to add functionality needed for initializing auctions from genesis.
 | 
			
		||||
type GenesisAuction interface {
 | 
			
		||||
	Auction
 | 
			
		||||
	GetModuleAccountCoins() sdk.Coins
 | 
			
		||||
	Validate() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenesisAuctions is a slice of genesis auctions.
 | 
			
		||||
type GenesisAuctions []GenesisAuction
 | 
			
		||||
 | 
			
		||||
// GenesisState is auction state that must be provided at chain genesis.
 | 
			
		||||
type GenesisState struct {
 | 
			
		||||
	NextAuctionID uint64   `json:"next_auction_id" yaml:"next_auction_id"`
 | 
			
		||||
	Params        Params   `json:"auction_params" yaml:"auction_params"`
 | 
			
		||||
	Auctions      Auctions `json:"genesis_auctions" yaml:"genesis_auctions"`
 | 
			
		||||
	NextAuctionID uint64          `json:"next_auction_id" yaml:"next_auction_id"`
 | 
			
		||||
	Params        Params          `json:"auction_params" yaml:"auction_params"`
 | 
			
		||||
	Auctions      GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewGenesisState returns a new genesis state object for auctions module.
 | 
			
		||||
func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState {
 | 
			
		||||
func NewGenesisState(nextID uint64, ap Params, ga GenesisAuctions) GenesisState {
 | 
			
		||||
	return GenesisState{
 | 
			
		||||
		NextAuctionID: nextID,
 | 
			
		||||
		Params:        ap,
 | 
			
		||||
@ -25,25 +35,42 @@ func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState {
 | 
			
		||||
 | 
			
		||||
// DefaultGenesisState returns the default genesis state for auction module.
 | 
			
		||||
func DefaultGenesisState() GenesisState {
 | 
			
		||||
	return NewGenesisState(0, DefaultParams(), Auctions{})
 | 
			
		||||
	return NewGenesisState(0, DefaultParams(), GenesisAuctions{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Equal checks whether two GenesisState structs are equivalent.
 | 
			
		||||
func (data GenesisState) Equal(data2 GenesisState) bool {
 | 
			
		||||
	b1 := ModuleCdc.MustMarshalBinaryBare(data)
 | 
			
		||||
	b2 := ModuleCdc.MustMarshalBinaryBare(data2)
 | 
			
		||||
func (gs GenesisState) Equal(gs2 GenesisState) bool {
 | 
			
		||||
	b1 := ModuleCdc.MustMarshalBinaryBare(gs)
 | 
			
		||||
	b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
 | 
			
		||||
	return bytes.Equal(b1, b2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsEmpty returns true if a GenesisState is empty.
 | 
			
		||||
func (data GenesisState) IsEmpty() bool {
 | 
			
		||||
	return data.Equal(GenesisState{})
 | 
			
		||||
func (gs GenesisState) IsEmpty() bool {
 | 
			
		||||
	return gs.Equal(GenesisState{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateGenesis validates genesis inputs. It returns error if validation of any input fails.
 | 
			
		||||
func ValidateGenesis(data GenesisState) error {
 | 
			
		||||
	if err := data.Params.Validate(); err != nil {
 | 
			
		||||
func (gs GenesisState) Validate() error {
 | 
			
		||||
	if err := gs.Params.Validate(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ids := map[uint64]bool{}
 | 
			
		||||
	for _, a := range gs.Auctions {
 | 
			
		||||
 | 
			
		||||
		if err := a.Validate(); err != nil {
 | 
			
		||||
			return fmt.Errorf("found invalid auction: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ids[a.GetID()] {
 | 
			
		||||
			return fmt.Errorf("found duplicate auction ID (%d)", a.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		ids[a.GetID()] = true
 | 
			
		||||
 | 
			
		||||
		if a.GetID() >= gs.NextAuctionID {
 | 
			
		||||
			return fmt.Errorf("found auction ID >= the nextAuctionID (%d >= %d)", a.GetID(), gs.NextAuctionID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										46
									
								
								x/auction/types/genesis_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								x/auction/types/genesis_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var testCoin = sdk.NewInt64Coin("test", 20)
 | 
			
		||||
 | 
			
		||||
func TestGenesisState_Validate(t *testing.T) {
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		nextID     uint64
 | 
			
		||||
		auctions   GenesisAuctions
 | 
			
		||||
		expectPass bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"default", DefaultGenesisState().NextAuctionID, DefaultGenesisState().Auctions, true},
 | 
			
		||||
		{"invalid next ID", 54, GenesisAuctions{SurplusAuction{BaseAuction{ID: 105}}}, false},
 | 
			
		||||
		{
 | 
			
		||||
			"repeated ID",
 | 
			
		||||
			1000,
 | 
			
		||||
			GenesisAuctions{
 | 
			
		||||
				SurplusAuction{BaseAuction{ID: 105}},
 | 
			
		||||
				DebtAuction{BaseAuction{ID: 105}, testCoin},
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			gs := NewGenesisState(tc.nextID, DefaultParams(), tc.auctions)
 | 
			
		||||
 | 
			
		||||
			err := gs.Validate()
 | 
			
		||||
 | 
			
		||||
			if tc.expectPass {
 | 
			
		||||
				require.NoError(t, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				require.Error(t, err)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user