Add min bid increments (#380)

* refactor bidding test

* add some more bid test cases

* add balance checks to bid tests

* add more checks to bid tests

* add min bid increments

* protect against negative lot amounts

* fix params tests

* change endblocker to beginblocker

* update spec

* fix params tests

* fix: update alias

Co-authored-by: Kevin Davis <karzak@users.noreply.github.com>
This commit is contained in:
Ruaridh 2020-02-28 22:16:22 +00:00 committed by GitHub
parent e72b20eb7d
commit 7eede47769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 521 additions and 195 deletions

View File

@ -271,9 +271,10 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
// During begin block slashing happens after distr.BeginBlocker so that // During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the // there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant. // CanWithdrawInvariant invariant.
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, cdp.ModuleName) // Auction.BeginBlocker will close out expired auctions and pay debt back to cdp. So it should be run before cdp.BeginBlocker which cancels out debt with stable and starts more auctions.
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, auction.ModuleName, cdp.ModuleName)
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName, auction.ModuleName) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
// Note: genutils must occur after staking so that pools are properly // Note: genutils must occur after staking so that pools are properly
// initialized with tokens from genesis accounts. // initialized with tokens from genesis accounts.

View File

@ -4,8 +4,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
// EndBlocker runs at the end of every block. // BeginBlocker runs at the start of every block.
func EndBlocker(ctx sdk.Context, k Keeper) { func BeginBlocker(ctx sdk.Context, k Keeper) {
err := k.CloseExpiredAuctions(ctx) err := k.CloseExpiredAuctions(ctx)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -15,7 +15,7 @@ import (
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
) )
func TestKeeper_EndBlocker(t *testing.T) { func TestKeeper_BeginBlocker(t *testing.T) {
// Setup // Setup
_, addrs := app.GeneratePrivKeyAddressPairs(2) _, addrs := app.GeneratePrivKeyAddressPairs(2)
buyer := addrs[0] buyer := addrs[0]
@ -36,13 +36,14 @@ func TestKeeper_EndBlocker(t *testing.T) {
ctx := tApp.NewContext(true, abci.Header{}) ctx := tApp.NewContext(true, abci.Header{})
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
// Start an auction and place a bid
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40)) auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30))) require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30)))
// Run the endblocker, simulating a block time 1ns before auction expiry // Run the beginblocker, simulating a block time 1ns before auction expiry
preExpiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration - 1) preExpiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration - 1)
auction.EndBlocker(ctx.WithBlockTime(preExpiryTime), keeper) auction.BeginBlocker(ctx.WithBlockTime(preExpiryTime), keeper)
// Check auction has not been closed yet // Check auction has not been closed yet
_, found := keeper.GetAuction(ctx, auctionID) _, found := keeper.GetAuction(ctx, auctionID)
@ -50,7 +51,7 @@ func TestKeeper_EndBlocker(t *testing.T) {
// Run the endblocker, simulating a block time equal to auction expiry // Run the endblocker, simulating a block time equal to auction expiry
expiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration) expiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration)
auction.EndBlocker(ctx.WithBlockTime(expiryTime), keeper) auction.BeginBlocker(ctx.WithBlockTime(expiryTime), keeper)
// Check auction has been closed // Check auction has been closed
_, found = keeper.GetAuction(ctx, auctionID) _, found = keeper.GetAuction(ctx, auctionID)

View File

@ -1,8 +1,8 @@
// nolint // nolint
// autogenerated code using github.com/rigelrozanski/multitool // autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories: // aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/auction/types/ // ALIASGEN: github.com/kava-labs/kava/x/auction/keeper
// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper/ // ALIASGEN: github.com/kava-labs/kava/x/auction/types
package auction package auction
import ( import (
@ -13,6 +13,7 @@ import (
const ( const (
DefaultCodespace = types.DefaultCodespace DefaultCodespace = types.DefaultCodespace
CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID
CodeInvalidModulePermissions = types.CodeInvalidModulePermissions
CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType
CodeAuctionNotFound = types.CodeAuctionNotFound CodeAuctionNotFound = types.CodeAuctionNotFound
CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired
@ -21,60 +22,101 @@ const (
CodeInvalidLotDenom = types.CodeInvalidLotDenom CodeInvalidLotDenom = types.CodeInvalidLotDenom
CodeBidTooSmall = types.CodeBidTooSmall CodeBidTooSmall = types.CodeBidTooSmall
CodeBidTooLarge = types.CodeBidTooLarge CodeBidTooLarge = types.CodeBidTooLarge
CodeLotTooSmall = types.CodeLotTooSmall
CodeLotTooLarge = types.CodeLotTooLarge CodeLotTooLarge = types.CodeLotTooLarge
CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase
CodeCollateralAuctionIsInForwardPhase = types.CodeCollateralAuctionIsInForwardPhase CodeCollateralAuctionIsInForwardPhase = types.CodeCollateralAuctionIsInForwardPhase
EventTypeAuctionStart = types.EventTypeAuctionStart
EventTypeAuctionBid = types.EventTypeAuctionBid
EventTypeAuctionClose = types.EventTypeAuctionClose
AttributeValueCategory = types.AttributeValueCategory
AttributeKeyAuctionID = types.AttributeKeyAuctionID
AttributeKeyAuctionType = types.AttributeKeyAuctionType
AttributeKeyBidder = types.AttributeKeyBidder
AttributeKeyBidDenom = types.AttributeKeyBidDenom
AttributeKeyLotDenom = types.AttributeKeyLotDenom
AttributeKeyBidAmount = types.AttributeKeyBidAmount
AttributeKeyLotAmount = types.AttributeKeyLotAmount
AttributeKeyEndTime = types.AttributeKeyEndTime
DefaultNextAuctionID = types.DefaultNextAuctionID
ModuleName = types.ModuleName ModuleName = types.ModuleName
StoreKey = types.StoreKey StoreKey = types.StoreKey
RouterKey = types.RouterKey RouterKey = types.RouterKey
DefaultParamspace = types.DefaultParamspace DefaultParamspace = types.DefaultParamspace
QuerierRoute = types.QuerierRoute
DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration
DefaultBidDuration = types.DefaultBidDuration DefaultBidDuration = types.DefaultBidDuration
QueryGetAuction = types.QueryGetAuction QueryGetAuction = types.QueryGetAuction
DefaultNextAuctionID = types.DefaultNextAuctionID QueryGetAuctions = types.QueryGetAuctions
QueryGetParams = types.QueryGetParams
) )
var ( var (
// functions aliases // functions aliases
NewSurplusAuction = types.NewSurplusAuction NewKeeper = keeper.NewKeeper
NewDebtAuction = types.NewDebtAuction NewQuerier = keeper.NewQuerier
NewCollateralAuction = types.NewCollateralAuction NewSurplusAuction = types.NewSurplusAuction
NewWeightedAddresses = types.NewWeightedAddresses NewDebtAuction = types.NewDebtAuction
RegisterCodec = types.RegisterCodec NewCollateralAuction = types.NewCollateralAuction
NewGenesisState = types.NewGenesisState NewWeightedAddresses = types.NewWeightedAddresses
DefaultGenesisState = types.DefaultGenesisState RegisterCodec = types.RegisterCodec
GetAuctionKey = types.GetAuctionKey ErrInvalidInitialAuctionID = types.ErrInvalidInitialAuctionID
GetAuctionByTimeKey = types.GetAuctionByTimeKey ErrInvalidModulePermissions = types.ErrInvalidModulePermissions
Uint64FromBytes = types.Uint64FromBytes ErrUnrecognizedAuctionType = types.ErrUnrecognizedAuctionType
Uint64ToBytes = types.Uint64ToBytes ErrAuctionNotFound = types.ErrAuctionNotFound
NewMsgPlaceBid = types.NewMsgPlaceBid ErrAuctionHasNotExpired = types.ErrAuctionHasNotExpired
NewParams = types.NewParams ErrAuctionHasExpired = types.ErrAuctionHasExpired
DefaultParams = types.DefaultParams ErrInvalidBidDenom = types.ErrInvalidBidDenom
ParamKeyTable = types.ParamKeyTable ErrInvalidLotDenom = types.ErrInvalidLotDenom
NewKeeper = keeper.NewKeeper ErrBidTooSmall = types.ErrBidTooSmall
NewQuerier = keeper.NewQuerier ErrBidTooLarge = types.ErrBidTooLarge
ErrLotTooSmall = types.ErrLotTooSmall
ErrLotTooLarge = types.ErrLotTooLarge
ErrCollateralAuctionIsInReversePhase = types.ErrCollateralAuctionIsInReversePhase
ErrCollateralAuctionIsInForwardPhase = types.ErrCollateralAuctionIsInForwardPhase
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
GetAuctionKey = types.GetAuctionKey
GetAuctionByTimeKey = types.GetAuctionByTimeKey
Uint64ToBytes = types.Uint64ToBytes
Uint64FromBytes = types.Uint64FromBytes
NewMsgPlaceBid = types.NewMsgPlaceBid
NewParams = types.NewParams
DefaultParams = types.DefaultParams
ParamKeyTable = types.ParamKeyTable
NewQueryAllAuctionParams = types.NewQueryAllAuctionParams
NewAuctionWithPhase = types.NewAuctionWithPhase
// variable aliases // variable aliases
DistantFuture = types.DistantFuture
ModuleCdc = types.ModuleCdc ModuleCdc = types.ModuleCdc
AuctionKeyPrefix = types.AuctionKeyPrefix AuctionKeyPrefix = types.AuctionKeyPrefix
AuctionByTimeKeyPrefix = types.AuctionByTimeKeyPrefix AuctionByTimeKeyPrefix = types.AuctionByTimeKeyPrefix
NextAuctionIDKey = types.NextAuctionIDKey NextAuctionIDKey = types.NextAuctionIDKey
KeyAuctionBidDuration = types.KeyAuctionBidDuration DefaultIncrement = types.DefaultIncrement
KeyAuctionDuration = types.KeyAuctionDuration KeyBidDuration = types.KeyBidDuration
KeyMaxAuctionDuration = types.KeyMaxAuctionDuration
KeyIncrementSurplus = types.KeyIncrementSurplus
KeyIncrementDebt = types.KeyIncrementDebt
KeyIncrementCollateral = types.KeyIncrementCollateral
) )
type ( type (
Auction = types.Auction Keeper = keeper.Keeper
BaseAuction = types.BaseAuction Auction = types.Auction
SurplusAuction = types.SurplusAuction Auctions = types.Auctions
DebtAuction = types.DebtAuction BaseAuction = types.BaseAuction
CollateralAuction = types.CollateralAuction SurplusAuction = types.SurplusAuction
WeightedAddresses = types.WeightedAddresses DebtAuction = types.DebtAuction
SupplyKeeper = types.SupplyKeeper CollateralAuction = types.CollateralAuction
GenesisAuctions = types.GenesisAuctions WeightedAddresses = types.WeightedAddresses
GenesisAuction = types.GenesisAuction SupplyKeeper = types.SupplyKeeper
GenesisState = types.GenesisState GenesisAuction = types.GenesisAuction
MsgPlaceBid = types.MsgPlaceBid GenesisAuctions = types.GenesisAuctions
Params = types.Params GenesisState = types.GenesisState
Keeper = keeper.Keeper MsgPlaceBid = types.MsgPlaceBid
Params = types.Params
QueryAuctionParams = types.QueryAuctionParams
QueryAllAuctionParams = types.QueryAllAuctionParams
AuctionWithPhase = types.AuctionWithPhase
) )

View File

@ -168,8 +168,14 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
if bid.Denom != a.Bid.Denom { if bid.Denom != a.Bid.Denom {
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom) return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
} }
if !a.Bid.IsLT(bid) { minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
return a, types.ErrBidTooSmall(k.codespace, bid, a.Bid) sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(k.GetParams(ctx).IncrementSurplus).RoundInt(),
),
)
if bid.Amount.LT(minNewBidAmt) {
return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt))
} }
// New bidder pays back old bidder // New bidder pays back old bidder
@ -225,8 +231,15 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
if a.IsReversePhase() { if a.IsReversePhase() {
return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID) return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID)
} }
if !a.Bid.IsLT(bid) { minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
return a, types.ErrBidTooSmall(k.codespace, bid, a.Bid) sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(k.GetParams(ctx).IncrementCollateral).RoundInt(),
),
)
minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
if bid.Amount.LT(minNewBidAmt) {
return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt))
} }
if a.MaxBid.IsLT(bid) { if a.MaxBid.IsLT(bid) {
return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid) return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid)
@ -294,8 +307,17 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc
if !a.IsReversePhase() { if !a.IsReversePhase() {
return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID) return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID)
} }
if !lot.IsLT(a.Lot) { maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
return a, types.ErrLotTooLarge(k.codespace, lot, a.Lot) sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(k.GetParams(ctx).IncrementCollateral).RoundInt(),
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt))
}
if lot.IsNegative() {
return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt()))
} }
// New bidder pays back old bidder // New bidder pays back old bidder
@ -351,8 +373,17 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
if lot.Denom != a.Lot.Denom { if lot.Denom != a.Lot.Denom {
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom) return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
} }
if !lot.IsLT(a.Lot) { maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
return a, types.ErrLotTooLarge(k.codespace, lot, a.Lot) sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(k.GetParams(ctx).IncrementDebt).RoundInt(),
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt))
}
if lot.IsNegative() {
return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt()))
} }
// New bidder pays back old bidder // New bidder pays back old bidder

View File

@ -19,11 +19,10 @@ import (
type AuctionType int type AuctionType int
const ( const (
Invalid AuctionType = 0 Invalid AuctionType = 0
Surplus AuctionType = 1 Surplus AuctionType = 1
Debt AuctionType = 2 Debt AuctionType = 2
CollateralPhase1 AuctionType = 3 Collateral AuctionType = 3
CollateralPhase2 AuctionType = 4
) )
func TestAuctionBidding(t *testing.T) { func TestAuctionBidding(t *testing.T) {
@ -47,14 +46,14 @@ func TestAuctionBidding(t *testing.T) {
} }
type bidArgs struct { type bidArgs struct {
bidder sdk.AccAddress bidder sdk.AccAddress
amount sdk.Coin amount sdk.Coin
secondBidder sdk.AccAddress
} }
tests := []struct { tests := []struct {
name string name string
auctionArgs auctionArgs auctionArgs auctionArgs
setupBids []bidArgs
bidArgs bidArgs bidArgs bidArgs
expectedError sdk.CodeType expectedError sdk.CodeType
expectedEndTime time.Time expectedEndTime time.Time
@ -65,17 +64,30 @@ func TestAuctionBidding(t *testing.T) {
{ {
"basic: auction doesn't exist", "basic: auction doesn't exist",
auctionArgs{Surplus, "", c("token1", 1), c("token2", 1), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Surplus, "", c("token1", 1), c("token2", 1), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token2", 10), nil}, nil,
bidArgs{buyer, c("token2", 10)},
types.CodeAuctionNotFound, types.CodeAuctionNotFound,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 10), c("token2", 10),
false, false,
}, },
{
"basic: closed auction",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token2", 10)},
types.CodeAuctionHasExpired,
types.DistantFuture,
nil,
c("token2", 0),
false,
},
{ {
"surplus: normal", "surplus: normal",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token2", 10), nil}, nil,
bidArgs{buyer, c("token2", 10)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -85,7 +97,8 @@ func TestAuctionBidding(t *testing.T) {
{ {
"surplus: second bidder", "surplus: second bidder",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token2", 10), secondBuyer}, []bidArgs{{buyer, c("token2", 10)}},
bidArgs{secondBuyer, c("token2", 11)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
secondBuyer, secondBuyer,
@ -95,27 +108,52 @@ func TestAuctionBidding(t *testing.T) {
{ {
"surplus: invalid bid denom", "surplus: invalid bid denom",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("badtoken", 10), nil}, nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidBidDenom, types.CodeInvalidBidDenom,
types.DistantFuture,
nil, // surplus auctions are created with initial bidder as a nil address
c("token2", 0),
false,
},
{
"surplus: invalid bid (less than)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
[]bidArgs{{buyer, c("token2", 100)}},
bidArgs{buyer, c("token2", 99)},
types.CodeBidTooSmall,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 10), c("token2", 100),
false, false,
}, },
{ {
"surplus: invalid bid (equal)", "surplus: invalid bid (equal)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token2", 0), nil}, nil,
bidArgs{buyer, c("token2", 0)}, // min bid is technically 0 at default 5%, but it's capped at 1
types.CodeBidTooSmall,
types.DistantFuture,
nil, // surplus auctions are created with initial bidder as a nil address
c("token2", 0),
false,
},
{
"surplus: invalid bid (less than min increment)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
[]bidArgs{{buyer, c("token2", 100)}},
bidArgs{buyer, c("token2", 104)}, // min bid is 105 at default 5%
types.CodeBidTooSmall, types.CodeBidTooSmall,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 10), c("token2", 100),
false, false,
}, },
{ {
"debt: normal", "debt: normal",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("token1", 10), nil}, nil,
bidArgs{buyer, c("token1", 10)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -124,8 +162,9 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"debt: second bidder", "debt: second bidder",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("token1", 10), secondBuyer}, []bidArgs{{buyer, c("token1", 10)}},
bidArgs{secondBuyer, c("token1", 9)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
secondBuyer, secondBuyer,
@ -134,28 +173,53 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"debt: invalid lot denom", "debt: invalid lot denom",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("badtoken", 10), nil}, nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidLotDenom, types.CodeInvalidLotDenom,
someTime.Add(types.DefaultBidDuration), types.DistantFuture,
buyer, supply.NewModuleAddress(modName),
c("token1", 20), c("token2", 100),
false, false,
}, },
{ {
"debt: invalid lot size (larger)", "debt: invalid lot size (larger)",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token1", 21), nil}, nil,
bidArgs{buyer, c("token1", 21)},
types.CodeLotTooLarge, types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration), types.DistantFuture,
buyer, supply.NewModuleAddress(modName),
c("token1", 20), c("token2", 100),
false,
},
{
"debt: invalid lot size (equal)",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 20)},
types.CodeLotTooLarge,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
},
{
"debt: invalid lot size (larger than min increment)",
auctionArgs{Debt, modName, c("token1", 60), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57
types.CodeLotTooLarge,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false, false,
}, },
{ {
"collateral [forward]: normal", "collateral [forward]: normal",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 10), nil}, nil,
bidArgs{buyer, c("token2", 10)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -164,8 +228,9 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"collateral [forward]: second bidder", "collateral [forward]: second bidder",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 10), secondBuyer}, []bidArgs{{buyer, c("token2", 10)}},
bidArgs{secondBuyer, c("token2", 11)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
secondBuyer, secondBuyer,
@ -174,18 +239,20 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"collateral [forward]: invalid bid denom", "collateral [forward]: invalid bid denom",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("badtoken", 10), nil}, nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidBidDenom, types.CodeInvalidBidDenom,
someTime.Add(types.DefaultBidDuration), types.DistantFuture,
buyer, nil,
c("token2", 10), c("token2", 0),
false, false,
}, },
{ {
"collateral [forward]: invalid bid size (smaller)", "collateral [forward]: invalid bid size (smaller)",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 0), nil}, // lot, bid []bidArgs{{buyer, c("token2", 10)}},
bidArgs{buyer, c("token2", 9)},
types.CodeBidTooSmall, types.CodeBidTooSmall,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -193,19 +260,54 @@ func TestAuctionBidding(t *testing.T) {
false, false,
}, },
{ {
"collateral [forward]: invalid bid size (greater than max)", "collateral [forward]: invalid bid size (equal)",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 101), nil}, // lot, bid nil,
types.CodeBidTooLarge, bidArgs{buyer, c("token2", 0)},
types.CodeBidTooSmall,
types.DistantFuture,
nil,
c("token2", 0),
false,
},
{
"collateral [forward]: invalid bid size (less than min increment)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}},
bidArgs{buyer, c("token2", 51)},
types.CodeBidTooSmall,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 10), c("token2", 50),
false,
},
{
"collateral [forward]: less than min increment but equal to maxBid",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 99)}},
bidArgs{buyer, c("token2", 100)}, // min bid at default 5% is 104
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 100),
true,
},
{
"collateral [forward]: invalid bid size (greater than max)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("token2", 101)},
types.CodeBidTooLarge,
types.DistantFuture,
nil,
c("token2", 0),
false, false,
}, },
{ {
"collateral [reverse]: normal", "collateral [reverse]: normal",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 15), nil}, []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 15)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -214,8 +316,9 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"collateral [reverse]: second bidder", "collateral [reverse]: second bidder",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 15), secondBuyer}, []bidArgs{{buyer, c("token2", 50)}, {buyer, c("token1", 15)}}, // put auction into reverse phase, and add a reverse phase bid
bidArgs{secondBuyer, c("token1", 14)},
sdk.CodeType(0), sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
secondBuyer, secondBuyer,
@ -224,28 +327,20 @@ func TestAuctionBidding(t *testing.T) {
}, },
{ {
"collateral [reverse]: invalid lot denom", "collateral [reverse]: invalid lot denom",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("badtoken", 15), nil}, []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("badtoken", 15)},
types.CodeInvalidLotDenom, types.CodeInvalidLotDenom,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 50), c("token2", 50),
false, false,
}, },
{
"collateral [reverse]: invalid lot size (equal)",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 20), nil},
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
},
{ {
"collateral [reverse]: invalid lot size (greater)", "collateral [reverse]: invalid lot size (greater)",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 21), nil}, []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 21)},
types.CodeLotTooLarge, types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
@ -253,13 +348,25 @@ func TestAuctionBidding(t *testing.T) {
false, false,
}, },
{ {
"basic: closed auction", "collateral [reverse]: invalid lot size (equal)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 10), nil}, []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
types.CodeAuctionHasExpired, bidArgs{buyer, c("token1", 20)},
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration), someTime.Add(types.DefaultBidDuration),
buyer, buyer,
c("token2", 10), c("token2", 50),
false,
},
{
"collateral [reverse]: invalid lot size (larger than min increment)",
auctionArgs{Collateral, modName, c("token1", 60), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false, false,
}, },
} }
@ -283,6 +390,7 @@ func TestAuctionBidding(t *testing.T) {
) )
ctx := tApp.NewContext(false, abci.Header{}) ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
bank := tApp.GetBankKeeper()
// Start Auction // Start Auction
var id uint64 var id uint64
@ -292,56 +400,84 @@ func TestAuctionBidding(t *testing.T) {
id, _ = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom) id, _ = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom)
case Debt: case Debt:
id, _ = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt) id, _ = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt)
case CollateralPhase1, CollateralPhase2: case Collateral:
id, _ = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson id, _ = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson
// Move CollateralAuction to debt phase by placing max bid
if tc.auctionArgs.auctionType == CollateralPhase2 {
err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.auctionArgs.bid)
require.NoError(t, err)
}
default: default:
t.Fail() t.Fail()
} }
// Place setup bids
for _, b := range tc.setupBids {
require.NoError(t, keeper.PlaceBid(ctx, id, b.bidder, b.amount))
}
// Close the auction early to test late bidding (if applicable) // Close the auction early to test late bidding (if applicable)
if strings.Contains(tc.name, "closed") { if strings.Contains(tc.name, "closed") {
ctx = ctx.WithBlockTime(types.DistantFuture.Add(1)) ctx = ctx.WithBlockTime(types.DistantFuture.Add(1))
} }
// Store some state for use in checks
oldAuction, found := keeper.GetAuction(ctx, id)
var oldBidder sdk.AccAddress
if found {
oldBidder = oldAuction.GetBidder()
}
oldBidderOldCoins := bank.GetCoins(ctx, oldBidder)
newBidderOldCoins := bank.GetCoins(ctx, tc.bidArgs.bidder)
// Place bid on auction // Place bid on auction
err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.bidArgs.amount) err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.bidArgs.amount)
// Place second bid from new bidder
if tc.bidArgs.secondBidder != nil {
// Set bid increase/decrease based on auction type, phase
var secondBid sdk.Coin
switch tc.auctionArgs.auctionType {
case Surplus, CollateralPhase1:
secondBid = tc.bidArgs.amount.Add(c(tc.bidArgs.amount.Denom, 1))
case Debt, CollateralPhase2:
secondBid = tc.bidArgs.amount.Sub(c(tc.bidArgs.amount.Denom, 1))
default:
t.Fail()
}
// Place the second bid
err2 := keeper.PlaceBid(ctx, id, tc.bidArgs.secondBidder, secondBid)
require.NoError(t, err2)
}
// Check success/failure // Check success/failure
if tc.expectpass { if tc.expectpass {
require.Nil(t, err) require.Nil(t, err)
// Get auction from store // Check auction was found
auction, found := keeper.GetAuction(ctx, id) newAuction, found := keeper.GetAuction(ctx, id)
require.True(t, found) require.True(t, found)
// Check auction values // Check auction values
require.Equal(t, modName, auction.GetInitiator()) require.Equal(t, modName, newAuction.GetInitiator())
require.Equal(t, tc.expectedBidder, auction.GetBidder()) require.Equal(t, tc.expectedBidder, newAuction.GetBidder())
require.Equal(t, tc.expectedBid, auction.GetBid()) require.Equal(t, tc.expectedBid, newAuction.GetBid())
require.Equal(t, tc.expectedEndTime, auction.GetEndTime()) require.Equal(t, tc.expectedEndTime, newAuction.GetEndTime())
// Check coins have moved between bidder and previous bidder
bidAmt := tc.bidArgs.amount
switch tc.auctionArgs.auctionType {
case Debt:
bidAmt = oldAuction.GetBid()
case Collateral:
collatAuction, _ := oldAuction.(types.CollateralAuction)
if collatAuction.IsReversePhase() {
bidAmt = oldAuction.GetBid()
}
}
if oldBidder.Equals(tc.bidArgs.bidder) { // same bidder
require.Equal(t, newBidderOldCoins.Sub(cs(bidAmt.Sub(oldAuction.GetBid()))), bank.GetCoins(ctx, tc.bidArgs.bidder))
} else {
require.Equal(t, cs(newBidderOldCoins.Sub(cs(bidAmt))...), bank.GetCoins(ctx, tc.bidArgs.bidder)) // wrapping in cs() to avoid comparing nil and empty coins
if oldBidder.Equals(supply.NewModuleAddress(oldAuction.GetInitiator())) { // handle checking debt coins for case debt auction has had no bids placed yet TODO make this less confusing
require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...).Add(cs(c("debt", oldAuction.GetBid().Amount.Int64()))), bank.GetCoins(ctx, oldBidder))
} else {
require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...), bank.GetCoins(ctx, oldBidder))
}
}
} else { } else {
// Check expected error code type // Check expected error code type
require.NotNil(t, err, "PlaceBid did not return an error") // catch nil values before they cause a panic below
require.Equal(t, tc.expectedError, err.Result().Code) require.Equal(t, tc.expectedError, err.Result().Code)
// Check auction values
newAuction, found := keeper.GetAuction(ctx, id)
if found {
require.Equal(t, modName, newAuction.GetInitiator())
require.Equal(t, tc.expectedBidder, newAuction.GetBidder())
require.Equal(t, tc.expectedBid, newAuction.GetBid())
require.Equal(t, tc.expectedEndTime, newAuction.GetEndTime())
}
// Check coins have not moved
require.Equal(t, newBidderOldCoins, bank.GetCoins(ctx, tc.bidArgs.bidder))
require.Equal(t, oldBidderOldCoins, bank.GetCoins(ctx, oldBidder))
} }
}) })
} }

View File

@ -149,10 +149,11 @@ func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
} }
// BeginBlock module begin-block // BeginBlock module begin-block
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}
// EndBlock module end-block // EndBlock module end-block
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
EndBlocker(ctx, am.keeper)
return []abci.ValidatorUpdate{} return []abci.ValidatorUpdate{}
} }

View File

@ -9,6 +9,9 @@
type Params struct { type Params struct {
MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction
MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry.
IncrementSurplus sdk.Dec `json:"increment_surplus" yaml:"increment_surplus"` // percentage change (of auc.Bid) required for a new bid on a surplus auction
IncrementDebt sdk.Dec `json:"increment_debt" yaml:"increment_debt"` // percentage change (of auc.Lot) required for a new bid on a debt auction
IncrementCollateral sdk.Dec `json:"increment_collateral" yaml:"increment_collateral"` // percentage change (of auc.Bid or auc.Lot) required for a new bid on a collateral auction
} }
``` ```

View File

@ -25,7 +25,7 @@ The `x/auction` module emits the following events:
| message | module | auction | | message | module | auction |
| message | sender | {sender address} | | message | sender | {sender address} |
## EndBlock ## BeginBlock
| Type | Attribute Key | Attribute Value | | Type | Attribute Key | Attribute Value |
|---------------|---------------|-----------------| |---------------|---------------|-----------------|

View File

@ -2,7 +2,10 @@
The auction module contains the following parameters: The auction module contains the following parameters:
| Key | Type | Example | | Key | Type | Example | Description |
| ------------------ | ---------------------- | -----------| |---------------------|------------------------|------------------------|---------------------------------------------------------------------------------------|
| MaxAuctionDuration | string (time.Duration) | "48h0m0s" | | MaxAuctionDuration | string (time.Duration) | "48h0m0s" | |
| BidDuration | string (time.Duration) | "3h0m0s" | | BidDuration | string (time.Duration) | "3h0m0s" | |
| IncrementSurplus | string (dec) | "0.050000000000000000" | percentage change in bid required for a new bid on a surplus auction |
| IncrementDebt | string (dec) | "0.050000000000000000" | percentage change in lot required for a new bid on a debt auction |
| IncrementCollateral | string (dec) | "0.050000000000000000" | percentage change in either bid or lot required for a new bid on a collateral auction |

View File

@ -1,6 +1,6 @@
# End Block # Begin Block
At the end of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows: At the start of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows:
```go ```go
var expiredAuctions []uint64 var expiredAuctions []uint64

View File

@ -6,7 +6,7 @@
3. **[Messages](03_messages.md)** 3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)** 4. **[Events](04_events.md)**
5. **[Params](05_params.md)** 5. **[Params](05_params.md)**
6. **[EndBlock](06_end_block.md)** 6. **[BeginBlock](06_begin_block.md)**
## Abstract ## Abstract

View File

@ -61,7 +61,7 @@ func (a BaseAuction) GetBid() sdk.Coin { return a.Bid }
// 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 }
// GetType returns theauction type. Used to identify auctions in event attributes. // GetType returns the auction type. Used to identify auctions in event attributes.
func (a BaseAuction) GetType() string { return "base" } func (a BaseAuction) GetType() string { return "base" }
// Validate verifies that the auction end time is before max end time // Validate verifies that the auction end time is before max end time

View File

@ -21,9 +21,10 @@ const (
CodeInvalidLotDenom sdk.CodeType = 8 CodeInvalidLotDenom sdk.CodeType = 8
CodeBidTooSmall sdk.CodeType = 9 CodeBidTooSmall sdk.CodeType = 9
CodeBidTooLarge sdk.CodeType = 10 CodeBidTooLarge sdk.CodeType = 10
CodeLotTooLarge sdk.CodeType = 11 CodeLotTooSmall sdk.CodeType = 11
CodeCollateralAuctionIsInReversePhase sdk.CodeType = 12 CodeLotTooLarge sdk.CodeType = 12
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 13 CodeCollateralAuctionIsInReversePhase sdk.CodeType = 13
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 14
) )
// ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set // ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set
@ -66,9 +67,9 @@ func ErrInvalidLotDenom(codespace sdk.CodespaceType, lotDenom string, auctionLot
return sdk.NewError(codespace, CodeInvalidLotDenom, fmt.Sprintf("lot denom %s doesn't match auction lot denom %s", lotDenom, auctionLotDenom)) return sdk.NewError(codespace, CodeInvalidLotDenom, fmt.Sprintf("lot denom %s doesn't match auction lot denom %s", lotDenom, auctionLotDenom))
} }
// ErrBidTooSmall error for when bid is not greater than auction's last bid // ErrBidTooSmall error for when bid is not greater than auction's min bid amount
func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, lastBid sdk.Coin) sdk.Error { func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, minBid sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeBidTooSmall, fmt.Sprintf("bid %s is not greater than auction's last bid %s", bid.String(), lastBid.String())) return sdk.NewError(codespace, CodeBidTooSmall, fmt.Sprintf("bid %s is not greater than auction's min new bid amount %s", bid.String(), minBid.String()))
} }
// ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid // ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid
@ -76,9 +77,14 @@ func ErrBidTooLarge(codespace sdk.CodespaceType, bid sdk.Coin, maxBid sdk.Coin)
return sdk.NewError(codespace, CodeBidTooLarge, fmt.Sprintf("bid %s is greater than auction's max bid %s", bid.String(), maxBid.String())) return sdk.NewError(codespace, CodeBidTooLarge, fmt.Sprintf("bid %s is greater than auction's max bid %s", bid.String(), maxBid.String()))
} }
// ErrLotTooLarge error for when lot is not smaller than auction's last lot // ErrLotToosmall error for when lot is less than zero
func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, lastLot sdk.Coin) sdk.Error { func ErrLotTooSmall(codespace sdk.CodespaceType, lot sdk.Coin, minLot sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeLotTooLarge, fmt.Sprintf("lot %s is not less than auction's last lot %s", lot.String(), lastLot.String())) return sdk.NewError(codespace, CodeLotTooSmall, fmt.Sprintf("lot %s is not greater than auction's min new lot amount %s", lot.String(), minLot.String()))
}
// ErrLotTooLarge error for when lot is not smaller than auction's max new lot amount
func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, maxLot sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeLotTooLarge, fmt.Sprintf("lot %s is greater than auction's max new lot amount %s", lot.String(), maxLot.String()))
} }
// ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase // ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase

View File

@ -17,26 +17,36 @@ const (
DefaultBidDuration time.Duration = 1 * time.Hour DefaultBidDuration time.Duration = 1 * time.Hour
) )
// Parameter keys
var ( var (
// DefaultIncrement is the smallest percent change a new bid must have from the old one
DefaultIncrement sdk.Dec = sdk.MustNewDecFromStr("0.05")
// ParamStoreKeyParams Param store key for auction params // ParamStoreKeyParams Param store key for auction params
KeyAuctionBidDuration = []byte("BidDuration") KeyBidDuration = []byte("BidDuration")
KeyAuctionDuration = []byte("MaxAuctionDuration") KeyMaxAuctionDuration = []byte("MaxAuctionDuration")
KeyIncrementSurplus = []byte("IncrementSurplus")
KeyIncrementDebt = []byte("IncrementDebt")
KeyIncrementCollateral = []byte("IncrementCollateral")
) )
var _ subspace.ParamSet = &Params{} var _ subspace.ParamSet = &Params{}
// Params is the governance parameters for the auction module. // Params is the governance parameters for the auction module.
type Params struct { type Params struct {
MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction
BidDuration time.Duration `json:"bid_duration" yaml:"bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. BidDuration time.Duration `json:"bid_duration" yaml:"bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry.
IncrementSurplus sdk.Dec `json:"increment_surplus" yaml:"increment_surplus"` // percentage change (of auc.Bid) required for a new bid on a surplus auction
IncrementDebt sdk.Dec `json:"increment_debt" yaml:"increment_debt"` // percentage change (of auc.Lot) required for a new bid on a debt auction
IncrementCollateral sdk.Dec `json:"increment_collateral" yaml:"increment_collateral"` // percentage change (of auc.Bid or auc.Lot) required for a new bid on a collateral auction
} }
// NewParams returns a new Params object. // NewParams returns a new Params object.
func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params { func NewParams(maxAuctionDuration, bidDuration time.Duration, incrementSurplus, incrementDebt, incrementCollateral sdk.Dec) Params {
return Params{ return Params{
MaxAuctionDuration: maxAuctionDuration, MaxAuctionDuration: maxAuctionDuration,
BidDuration: bidDuration, BidDuration: bidDuration,
IncrementSurplus: incrementSurplus,
IncrementDebt: incrementDebt,
IncrementCollateral: incrementCollateral,
} }
} }
@ -45,6 +55,9 @@ func DefaultParams() Params {
return NewParams( return NewParams(
DefaultMaxAuctionDuration, DefaultMaxAuctionDuration,
DefaultBidDuration, DefaultBidDuration,
DefaultIncrement,
DefaultIncrement,
DefaultIncrement,
) )
} }
@ -56,8 +69,11 @@ func ParamKeyTable() subspace.KeyTable {
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs. // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs.
func (p *Params) ParamSetPairs() subspace.ParamSetPairs { func (p *Params) ParamSetPairs() subspace.ParamSetPairs {
return subspace.ParamSetPairs{ return subspace.ParamSetPairs{
{Key: KeyAuctionBidDuration, Value: &p.BidDuration}, {Key: KeyBidDuration, Value: &p.BidDuration},
{Key: KeyAuctionDuration, Value: &p.MaxAuctionDuration}, {Key: KeyMaxAuctionDuration, Value: &p.MaxAuctionDuration},
{Key: KeyIncrementSurplus, Value: &p.IncrementSurplus},
{Key: KeyIncrementDebt, Value: &p.IncrementDebt},
{Key: KeyIncrementCollateral, Value: &p.IncrementCollateral},
} }
} }
@ -72,7 +88,11 @@ func (p Params) Equal(p2 Params) bool {
func (p Params) String() string { func (p Params) String() string {
return fmt.Sprintf(`Auction Params: return fmt.Sprintf(`Auction Params:
Max Auction Duration: %s Max Auction Duration: %s
Bid Duration: %s`, p.MaxAuctionDuration, p.BidDuration) Bid Duration: %s
Increment Surplus: %s
Increment Debt: %s
Increment Collateral: %s`,
p.MaxAuctionDuration, p.BidDuration, p.IncrementSurplus, p.IncrementDebt, p.IncrementCollateral)
} }
// Validate checks that the parameters have valid values. // Validate checks that the parameters have valid values.
@ -86,5 +106,17 @@ func (p Params) Validate() error {
if p.BidDuration > p.MaxAuctionDuration { if p.BidDuration > p.MaxAuctionDuration {
return sdk.ErrInternal("bid duration param cannot be larger than max auction duration") return sdk.ErrInternal("bid duration param cannot be larger than max auction duration")
} }
if p.IncrementSurplus == (sdk.Dec{}) || p.IncrementDebt == (sdk.Dec{}) || p.IncrementCollateral == (sdk.Dec{}) {
return sdk.ErrInternal("auction increment values cannot be nil")
}
if p.IncrementSurplus.IsNegative() {
return sdk.ErrInternal("surplus auction increment cannot be less than zero")
}
if p.IncrementDebt.IsNegative() {
return sdk.ErrInternal("debt auction increment cannot be less than zero")
}
if p.IncrementCollateral.IsNegative() {
return sdk.ErrInternal("collateral auction increment cannot be less than zero")
}
return nil return nil
} }

View File

@ -1,33 +1,101 @@
package types package types
import ( import (
"github.com/stretchr/testify/require"
"testing" "testing"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
) )
func TestParams_Validate(t *testing.T) { func TestParams_Validate(t *testing.T) {
type fields struct { type fields struct {
} }
testCases := []struct { testCases := []struct {
name string name string
MaxAuctionDuration time.Duration Params
BidDuration time.Duration expectErr bool
expectErr bool
}{ }{
{"normal", 24 * time.Hour, 1 * time.Hour, false}, {
{"negativeBid", 24 * time.Hour, -1 * time.Hour, true}, "normal",
{"negativeAuction", -24 * time.Hour, 1 * time.Hour, true}, DefaultParams(),
{"bid>auction", 1 * time.Hour, 24 * time.Hour, true}, false,
{"zeros", 0, 0, false}, },
{
"negativeBid",
Params{
MaxAuctionDuration: 24 * time.Hour,
BidDuration: -1 * time.Hour,
IncrementSurplus: d("0.05"),
IncrementDebt: d("0.05"),
IncrementCollateral: d("0.05"),
},
true,
},
{
"negativeAuction",
Params{
MaxAuctionDuration: -24 * time.Hour,
BidDuration: 1 * time.Hour,
IncrementSurplus: d("0.05"),
IncrementDebt: d("0.05"),
IncrementCollateral: d("0.05"),
},
true,
},
{
"bid>auction",
Params{
MaxAuctionDuration: 1 * time.Hour,
BidDuration: 24 * time.Hour,
IncrementSurplus: d("0.05"),
IncrementDebt: d("0.05"),
IncrementCollateral: d("0.05"),
},
true,
},
{
"negative increment surplus",
Params{
MaxAuctionDuration: 24 * time.Hour,
BidDuration: 1 * time.Hour,
IncrementSurplus: d("-0.05"),
IncrementDebt: d("0.05"),
IncrementCollateral: d("0.05"),
},
true,
},
{
"negative increment debt",
Params{
MaxAuctionDuration: 24 * time.Hour,
BidDuration: 1 * time.Hour,
IncrementSurplus: d("0.05"),
IncrementDebt: d("-0.05"),
IncrementCollateral: d("0.05"),
},
true,
},
{
"negative increment collateral",
Params{
MaxAuctionDuration: 24 * time.Hour,
BidDuration: 1 * time.Hour,
IncrementSurplus: d("0.05"),
IncrementDebt: d("0.05"),
IncrementCollateral: d("-0.05"),
},
true,
},
{
"zero value",
Params{},
true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
p := Params{ err := tc.Params.Validate()
MaxAuctionDuration: tc.MaxAuctionDuration,
BidDuration: tc.BidDuration,
}
err := p.Validate()
if tc.expectErr { if tc.expectErr {
require.Error(t, err) require.Error(t, err)
} else { } else {
@ -36,3 +104,5 @@ func TestParams_Validate(t *testing.T) {
}) })
} }
} }
func d(amount string) sdk.Dec { return sdk.MustNewDecFromStr(amount) }