mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +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),
|
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
|
||||||
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
|
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
|
||||||
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
|
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
|
||||||
auction.NewAppModule(app.auctionKeeper),
|
auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
|
||||||
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper),
|
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper),
|
||||||
pricefeed.NewAppModule(app.pricefeedKeeper),
|
pricefeed.NewAppModule(app.pricefeedKeeper),
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,6 @@ var (
|
|||||||
RegisterCodec = types.RegisterCodec
|
RegisterCodec = types.RegisterCodec
|
||||||
NewGenesisState = types.NewGenesisState
|
NewGenesisState = types.NewGenesisState
|
||||||
DefaultGenesisState = types.DefaultGenesisState
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
ValidateGenesis = types.ValidateGenesis
|
|
||||||
GetAuctionKey = types.GetAuctionKey
|
GetAuctionKey = types.GetAuctionKey
|
||||||
GetAuctionByTimeKey = types.GetAuctionByTimeKey
|
GetAuctionByTimeKey = types.GetAuctionByTimeKey
|
||||||
Uint64FromBytes = types.Uint64FromBytes
|
Uint64FromBytes = types.Uint64FromBytes
|
||||||
@ -58,7 +57,8 @@ type (
|
|||||||
CollateralAuction = types.CollateralAuction
|
CollateralAuction = types.CollateralAuction
|
||||||
WeightedAddresses = types.WeightedAddresses
|
WeightedAddresses = types.WeightedAddresses
|
||||||
SupplyKeeper = types.SupplyKeeper
|
SupplyKeeper = types.SupplyKeeper
|
||||||
Auctions = types.Auctions
|
GenesisAuctions = types.GenesisAuctions
|
||||||
|
GenesisAuction = types.GenesisAuction
|
||||||
GenesisState = types.GenesisState
|
GenesisState = types.GenesisState
|
||||||
MsgPlaceBid = types.MsgPlaceBid
|
MsgPlaceBid = types.MsgPlaceBid
|
||||||
Params = types.Params
|
Params = types.Params
|
||||||
|
@ -1,17 +1,39 @@
|
|||||||
package auction
|
package auction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/auction/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitGenesis initializes the store state from genesis data.
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
|
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper, gs GenesisState) {
|
||||||
keeper.SetNextAuctionID(ctx, data.NextAuctionID)
|
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)
|
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)
|
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 {
|
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
|
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"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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.
|
// NewHandler returns a function to handle all "auction" type messages.
|
||||||
func NewHandler(keeper Keeper) sdk.Handler {
|
func NewHandler(keeper Keeper) sdk.Handler {
|
||||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case MsgPlaceBid:
|
case MsgPlaceBid:
|
||||||
return handleMsgPlaceBid(ctx, keeper, msg)
|
return handleMsgPlaceBid(ctx, keeper, msg)
|
||||||
@ -26,5 +30,15 @@ func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Resu
|
|||||||
return err.Result()
|
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,
|
seller,
|
||||||
lot,
|
lot,
|
||||||
bidDenom,
|
bidDenom,
|
||||||
ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration))
|
types.DistantFuture)
|
||||||
|
|
||||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
|
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -27,6 +27,16 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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
|
return auctionID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +47,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
|
|||||||
buyer,
|
buyer,
|
||||||
bid,
|
bid,
|
||||||
initialLot,
|
initialLot,
|
||||||
ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration),
|
types.DistantFuture,
|
||||||
debt)
|
debt)
|
||||||
|
|
||||||
// This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker.
|
// 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 {
|
if err != nil {
|
||||||
return 0, err
|
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
|
return auctionID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +88,7 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
|
|||||||
auction := types.NewCollateralAuction(
|
auction := types.NewCollateralAuction(
|
||||||
seller,
|
seller,
|
||||||
lot,
|
lot,
|
||||||
ctx.BlockTime().Add(types.DefaultMaxAuctionDuration),
|
types.DistantFuture,
|
||||||
maxBid,
|
maxBid,
|
||||||
weightedAddresses,
|
weightedAddresses,
|
||||||
debt)
|
debt)
|
||||||
@ -86,6 +106,16 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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
|
return auctionID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +158,7 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres
|
|||||||
}
|
}
|
||||||
|
|
||||||
k.SetAuction(ctx, updatedAuction)
|
k.SetAuction(ctx, updatedAuction)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +173,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New bidder pays back old 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() {
|
if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() {
|
||||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -166,7 +197,21 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
|
|||||||
// Update Auction
|
// Update Auction
|
||||||
a.Bidder = bidder
|
a.Bidder = bidder
|
||||||
a.Bid = bid
|
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
|
return a, nil
|
||||||
}
|
}
|
||||||
@ -216,13 +261,26 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
|
|||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
|
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
|
// Update Auction
|
||||||
a.Bidder = bidder
|
a.Bidder = bidder
|
||||||
a.Bid = bid
|
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
|
return a, nil
|
||||||
}
|
}
|
||||||
@ -271,7 +329,21 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc
|
|||||||
// Update Auction
|
// Update Auction
|
||||||
a.Bidder = bidder
|
a.Bidder = bidder
|
||||||
a.Lot = lot
|
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
|
return a, nil
|
||||||
}
|
}
|
||||||
@ -312,13 +384,26 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
|
|||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
|
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
|
// Update Auction
|
||||||
a.Bidder = bidder
|
a.Bidder = bidder
|
||||||
a.Lot = lot
|
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
|
return a, nil
|
||||||
}
|
}
|
||||||
@ -354,6 +439,13 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
k.DeleteAuction(ctx, auctionID)
|
k.DeleteAuction(ctx, auctionID)
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeAuctionClose,
|
||||||
|
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
|
||||||
|
),
|
||||||
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,13 +227,14 @@ func TestStartSurplusAuction(t *testing.T) {
|
|||||||
// check auction in store and is correct
|
// check auction in store and is correct
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{
|
expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Initiator: tc.args.seller,
|
Initiator: tc.args.seller,
|
||||||
Lot: tc.args.lot,
|
Lot: tc.args.lot,
|
||||||
Bidder: nil,
|
Bidder: nil,
|
||||||
Bid: c(tc.args.bidDenom, 0),
|
Bid: c(tc.args.bidDenom, 0),
|
||||||
EndTime: tc.blockTime.Add(types.DefaultMaxAuctionDuration),
|
HasReceivedBids: false,
|
||||||
MaxEndTime: tc.blockTime.Add(types.DefaultMaxAuctionDuration),
|
EndTime: types.DistantFuture,
|
||||||
|
MaxEndTime: types.DistantFuture,
|
||||||
}})
|
}})
|
||||||
require.Equal(t, expectedAuction, actualAuc)
|
require.Equal(t, expectedAuction, actualAuc)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,18 +2,19 @@ package auction
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/auction/client/cli"
|
"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/client/rest"
|
||||||
|
"github.com/kava-labs/kava/x/auction/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -41,12 +42,12 @@ func (AppModuleBasic) DefaultGenesis() json.RawMessage {
|
|||||||
|
|
||||||
// ValidateGenesis performs genesis state validation for the auction module.
|
// ValidateGenesis performs genesis state validation for the auction module.
|
||||||
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
||||||
var data GenesisState
|
var gs GenesisState
|
||||||
err := ModuleCdc.UnmarshalJSON(bz, &data)
|
err := ModuleCdc.UnmarshalJSON(bz, &gs)
|
||||||
if err != nil {
|
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.
|
// 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.
|
// AppModule implements the sdk.AppModule interface.
|
||||||
type AppModule struct {
|
type AppModule struct {
|
||||||
AppModuleBasic
|
AppModuleBasic
|
||||||
keeper Keeper
|
|
||||||
|
keeper Keeper
|
||||||
|
supplyKeeper types.SupplyKeeper
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppModule creates a new AppModule object
|
// NewAppModule creates a new AppModule object
|
||||||
func NewAppModule(keeper Keeper) AppModule {
|
func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule {
|
||||||
return AppModule{
|
return AppModule{
|
||||||
AppModuleBasic: AppModuleBasic{},
|
AppModuleBasic: AppModuleBasic{},
|
||||||
keeper: keeper,
|
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 {
|
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
|
||||||
var genesisState GenesisState
|
var genesisState GenesisState
|
||||||
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
|
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
|
||||||
InitGenesis(ctx, am.keeper, genesisState)
|
InitGenesis(ctx, am.keeper, am.supplyKeeper, genesisState)
|
||||||
return []abci.ValidatorUpdate{}
|
return []abci.ValidatorUpdate{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,32 @@
|
|||||||
# Events
|
# Events
|
||||||
|
|
||||||
<!--
|
The `x/auction` module emits the following events:
|
||||||
TODO: Add events for auction_start, auction_end, auction_bid
|
|
||||||
-->
|
## 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"
|
"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.
|
// Auction is an interface for handling common actions on auctions.
|
||||||
type Auction interface {
|
type Auction interface {
|
||||||
GetID() uint64
|
GetID() uint64
|
||||||
@ -17,13 +22,14 @@ type Auction interface {
|
|||||||
|
|
||||||
// BaseAuction is a common type shared by all Auctions.
|
// BaseAuction is a common type shared by all Auctions.
|
||||||
type BaseAuction struct {
|
type BaseAuction struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Initiator string // Module name that starts the auction. Pays out Lot.
|
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.
|
Lot sdk.Coin // Coins that will paid out by Initiator to the winning bidder.
|
||||||
Bidder sdk.AccAddress // Latest bidder. Receiver of Lot.
|
Bidder sdk.AccAddress // Latest bidder. Receiver of Lot.
|
||||||
Bid sdk.Coin // Coins paid into the auction the bidder.
|
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.
|
HasReceivedBids bool // Whether the auction has received any bids or not.
|
||||||
MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after.
|
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.
|
// 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.
|
// GetEndTime is a getter for auction end time.
|
||||||
func (a BaseAuction) GetEndTime() time.Time { return a.EndTime }
|
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 {
|
func (a BaseAuction) String() string {
|
||||||
return fmt.Sprintf(`Auction %d:
|
return fmt.Sprintf(`Auction %d:
|
||||||
Initiator: %s
|
Initiator: %s
|
||||||
@ -55,16 +68,27 @@ type SurplusAuction struct {
|
|||||||
// WithID returns an auction with the ID set.
|
// WithID returns an auction with the ID set.
|
||||||
func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
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.
|
// NewSurplusAuction returns a new surplus auction.
|
||||||
func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction {
|
func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction {
|
||||||
auction := SurplusAuction{BaseAuction{
|
auction := SurplusAuction{BaseAuction{
|
||||||
// no ID
|
// no ID
|
||||||
Initiator: seller,
|
Initiator: seller,
|
||||||
Lot: lot,
|
Lot: lot,
|
||||||
Bidder: nil,
|
Bidder: nil,
|
||||||
Bid: sdk.NewInt64Coin(bidDenom, 0),
|
Bid: sdk.NewInt64Coin(bidDenom, 0),
|
||||||
EndTime: endTime,
|
HasReceivedBids: false, // new auctions don't have any bids
|
||||||
MaxEndTime: endTime,
|
EndTime: endTime,
|
||||||
|
MaxEndTime: endTime,
|
||||||
}}
|
}}
|
||||||
return auction
|
return auction
|
||||||
}
|
}
|
||||||
@ -79,20 +103,32 @@ type DebtAuction struct {
|
|||||||
// WithID returns an auction with the ID set.
|
// WithID returns an auction with the ID set.
|
||||||
func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
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.
|
// 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)
|
// 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.
|
// 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.
|
// 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{
|
auction := DebtAuction{
|
||||||
BaseAuction: BaseAuction{
|
BaseAuction: BaseAuction{
|
||||||
// no ID
|
// no ID
|
||||||
Initiator: buyerModAccName,
|
Initiator: buyerModAccName,
|
||||||
Lot: initialLot,
|
Lot: initialLot,
|
||||||
Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer.
|
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
|
Bid: bid, // amount that the buyer is buying - doesn't change over course of auction
|
||||||
EndTime: EndTime,
|
HasReceivedBids: false, // new auctions don't have any bids
|
||||||
MaxEndTime: EndTime},
|
EndTime: endTime,
|
||||||
|
MaxEndTime: endTime},
|
||||||
CorrespondingDebt: debt,
|
CorrespondingDebt: debt,
|
||||||
}
|
}
|
||||||
return auction
|
return auction
|
||||||
@ -113,6 +149,16 @@ type CollateralAuction struct {
|
|||||||
// WithID returns an auction with the ID set.
|
// WithID returns an auction with the ID set.
|
||||||
func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
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.
|
// IsReversePhase returns whether the auction has switched over to reverse phase or not.
|
||||||
// Auction initially start in forward phase.
|
// Auction initially start in forward phase.
|
||||||
func (a CollateralAuction) IsReversePhase() bool {
|
func (a CollateralAuction) IsReversePhase() bool {
|
||||||
@ -136,16 +182,17 @@ func (a CollateralAuction) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCollateralAuction returns a new collateral auction.
|
// 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{
|
auction := CollateralAuction{
|
||||||
BaseAuction: BaseAuction{
|
BaseAuction: BaseAuction{
|
||||||
// no ID
|
// no ID
|
||||||
Initiator: seller,
|
Initiator: seller,
|
||||||
Lot: lot,
|
Lot: lot,
|
||||||
Bidder: nil,
|
Bidder: nil,
|
||||||
Bid: sdk.NewInt64Coin(maxBid.Denom, 0),
|
Bid: sdk.NewInt64Coin(maxBid.Denom, 0),
|
||||||
EndTime: EndTime,
|
HasReceivedBids: false, // new auctions don't have any bids
|
||||||
MaxEndTime: EndTime},
|
EndTime: endTime,
|
||||||
|
MaxEndTime: endTime},
|
||||||
CorrespondingDebt: debt,
|
CorrespondingDebt: debt,
|
||||||
MaxBid: maxBid,
|
MaxBid: maxBid,
|
||||||
LotReturns: lotReturns,
|
LotReturns: lotReturns,
|
||||||
|
@ -15,6 +15,7 @@ func init() {
|
|||||||
func RegisterCodec(cdc *codec.Codec) {
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
cdc.RegisterConcrete(MsgPlaceBid{}, "auction/MsgPlaceBid", nil)
|
cdc.RegisterConcrete(MsgPlaceBid{}, "auction/MsgPlaceBid", nil)
|
||||||
|
|
||||||
|
cdc.RegisterInterface((*GenesisAuction)(nil), nil)
|
||||||
cdc.RegisterInterface((*Auction)(nil), nil)
|
cdc.RegisterInterface((*Auction)(nil), nil)
|
||||||
cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil)
|
cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil)
|
||||||
cdc.RegisterConcrete(DebtAuction{}, "auction/DebtAuction", 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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auctions is a slice of auctions.
|
// GenesisAuction is an interface that extends the auction interface to add functionality needed for initializing auctions from genesis.
|
||||||
type Auctions []Auction
|
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.
|
// GenesisState is auction state that must be provided at chain genesis.
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"`
|
NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"`
|
||||||
Params Params `json:"auction_params" yaml:"auction_params"`
|
Params Params `json:"auction_params" yaml:"auction_params"`
|
||||||
Auctions Auctions `json:"genesis_auctions" yaml:"genesis_auctions"`
|
Auctions GenesisAuctions `json:"genesis_auctions" yaml:"genesis_auctions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState returns a new genesis state object for auctions module.
|
// 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{
|
return GenesisState{
|
||||||
NextAuctionID: nextID,
|
NextAuctionID: nextID,
|
||||||
Params: ap,
|
Params: ap,
|
||||||
@ -25,25 +35,42 @@ func NewGenesisState(nextID uint64, ap Params, ga Auctions) GenesisState {
|
|||||||
|
|
||||||
// DefaultGenesisState returns the default genesis state for auction module.
|
// DefaultGenesisState returns the default genesis state for auction module.
|
||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return NewGenesisState(0, DefaultParams(), Auctions{})
|
return NewGenesisState(0, DefaultParams(), GenesisAuctions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal checks whether two GenesisState structs are equivalent.
|
// Equal checks whether two GenesisState structs are equivalent.
|
||||||
func (data GenesisState) Equal(data2 GenesisState) bool {
|
func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
||||||
b1 := ModuleCdc.MustMarshalBinaryBare(data)
|
b1 := ModuleCdc.MustMarshalBinaryBare(gs)
|
||||||
b2 := ModuleCdc.MustMarshalBinaryBare(data2)
|
b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
|
||||||
return bytes.Equal(b1, b2)
|
return bytes.Equal(b1, b2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty returns true if a GenesisState is empty.
|
// IsEmpty returns true if a GenesisState is empty.
|
||||||
func (data GenesisState) IsEmpty() bool {
|
func (gs GenesisState) IsEmpty() bool {
|
||||||
return data.Equal(GenesisState{})
|
return gs.Equal(GenesisState{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateGenesis validates genesis inputs. It returns error if validation of any input fails.
|
// ValidateGenesis validates genesis inputs. It returns error if validation of any input fails.
|
||||||
func ValidateGenesis(data GenesisState) error {
|
func (gs GenesisState) Validate() error {
|
||||||
if err := data.Params.Validate(); err != nil {
|
if err := gs.Params.Validate(); err != nil {
|
||||||
return err
|
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
|
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