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
// there is nothing left over in the validator fee pool, so as to keep the
// 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
// initialized with tokens from genesis accounts.

View File

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

View File

@ -15,7 +15,7 @@ import (
"github.com/kava-labs/kava/x/cdp"
)
func TestKeeper_EndBlocker(t *testing.T) {
func TestKeeper_BeginBlocker(t *testing.T) {
// Setup
_, addrs := app.GeneratePrivKeyAddressPairs(2)
buyer := addrs[0]
@ -36,13 +36,14 @@ func TestKeeper_EndBlocker(t *testing.T) {
ctx := tApp.NewContext(true, abci.Header{})
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))
require.NoError(t, err)
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)
auction.EndBlocker(ctx.WithBlockTime(preExpiryTime), keeper)
auction.BeginBlocker(ctx.WithBlockTime(preExpiryTime), keeper)
// Check auction has not been closed yet
_, 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
expiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration)
auction.EndBlocker(ctx.WithBlockTime(expiryTime), keeper)
auction.BeginBlocker(ctx.WithBlockTime(expiryTime), keeper)
// Check auction has been closed
_, found = keeper.GetAuction(ctx, auctionID)

View File

@ -1,8 +1,8 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// 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
import (
@ -13,6 +13,7 @@ import (
const (
DefaultCodespace = types.DefaultCodespace
CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID
CodeInvalidModulePermissions = types.CodeInvalidModulePermissions
CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType
CodeAuctionNotFound = types.CodeAuctionNotFound
CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired
@ -21,60 +22,101 @@ const (
CodeInvalidLotDenom = types.CodeInvalidLotDenom
CodeBidTooSmall = types.CodeBidTooSmall
CodeBidTooLarge = types.CodeBidTooLarge
CodeLotTooSmall = types.CodeLotTooSmall
CodeLotTooLarge = types.CodeLotTooLarge
CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase
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
StoreKey = types.StoreKey
RouterKey = types.RouterKey
DefaultParamspace = types.DefaultParamspace
QuerierRoute = types.QuerierRoute
DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration
DefaultBidDuration = types.DefaultBidDuration
QueryGetAuction = types.QueryGetAuction
DefaultNextAuctionID = types.DefaultNextAuctionID
QueryGetAuctions = types.QueryGetAuctions
QueryGetParams = types.QueryGetParams
)
var (
// functions aliases
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
NewSurplusAuction = types.NewSurplusAuction
NewDebtAuction = types.NewDebtAuction
NewCollateralAuction = types.NewCollateralAuction
NewWeightedAddresses = types.NewWeightedAddresses
RegisterCodec = types.RegisterCodec
ErrInvalidInitialAuctionID = types.ErrInvalidInitialAuctionID
ErrInvalidModulePermissions = types.ErrInvalidModulePermissions
ErrUnrecognizedAuctionType = types.ErrUnrecognizedAuctionType
ErrAuctionNotFound = types.ErrAuctionNotFound
ErrAuctionHasNotExpired = types.ErrAuctionHasNotExpired
ErrAuctionHasExpired = types.ErrAuctionHasExpired
ErrInvalidBidDenom = types.ErrInvalidBidDenom
ErrInvalidLotDenom = types.ErrInvalidLotDenom
ErrBidTooSmall = types.ErrBidTooSmall
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
Uint64FromBytes = types.Uint64FromBytes
Uint64ToBytes = types.Uint64ToBytes
Uint64FromBytes = types.Uint64FromBytes
NewMsgPlaceBid = types.NewMsgPlaceBid
NewParams = types.NewParams
DefaultParams = types.DefaultParams
ParamKeyTable = types.ParamKeyTable
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
NewQueryAllAuctionParams = types.NewQueryAllAuctionParams
NewAuctionWithPhase = types.NewAuctionWithPhase
// variable aliases
DistantFuture = types.DistantFuture
ModuleCdc = types.ModuleCdc
AuctionKeyPrefix = types.AuctionKeyPrefix
AuctionByTimeKeyPrefix = types.AuctionByTimeKeyPrefix
NextAuctionIDKey = types.NextAuctionIDKey
KeyAuctionBidDuration = types.KeyAuctionBidDuration
KeyAuctionDuration = types.KeyAuctionDuration
DefaultIncrement = types.DefaultIncrement
KeyBidDuration = types.KeyBidDuration
KeyMaxAuctionDuration = types.KeyMaxAuctionDuration
KeyIncrementSurplus = types.KeyIncrementSurplus
KeyIncrementDebt = types.KeyIncrementDebt
KeyIncrementCollateral = types.KeyIncrementCollateral
)
type (
Keeper = keeper.Keeper
Auction = types.Auction
Auctions = types.Auctions
BaseAuction = types.BaseAuction
SurplusAuction = types.SurplusAuction
DebtAuction = types.DebtAuction
CollateralAuction = types.CollateralAuction
WeightedAddresses = types.WeightedAddresses
SupplyKeeper = types.SupplyKeeper
GenesisAuctions = types.GenesisAuctions
GenesisAuction = types.GenesisAuction
GenesisAuctions = types.GenesisAuctions
GenesisState = types.GenesisState
MsgPlaceBid = types.MsgPlaceBid
Params = types.Params
Keeper = keeper.Keeper
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 {
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
}
if !a.Bid.IsLT(bid) {
return a, types.ErrBidTooSmall(k.codespace, bid, a.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
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
@ -225,8 +231,15 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
if a.IsReversePhase() {
return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID)
}
if !a.Bid.IsLT(bid) {
return a, types.ErrBidTooSmall(k.codespace, bid, a.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
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) {
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() {
return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID)
}
if !lot.IsLT(a.Lot) {
return a, types.ErrLotTooLarge(k.codespace, lot, 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
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
@ -351,8 +373,17 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
if lot.Denom != a.Lot.Denom {
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
}
if !lot.IsLT(a.Lot) {
return a, types.ErrLotTooLarge(k.codespace, lot, 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
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

View File

@ -22,8 +22,7 @@ const (
Invalid AuctionType = 0
Surplus AuctionType = 1
Debt AuctionType = 2
CollateralPhase1 AuctionType = 3
CollateralPhase2 AuctionType = 4
Collateral AuctionType = 3
)
func TestAuctionBidding(t *testing.T) {
@ -49,12 +48,12 @@ func TestAuctionBidding(t *testing.T) {
type bidArgs struct {
bidder sdk.AccAddress
amount sdk.Coin
secondBidder sdk.AccAddress
}
tests := []struct {
name string
auctionArgs auctionArgs
setupBids []bidArgs
bidArgs bidArgs
expectedError sdk.CodeType
expectedEndTime time.Time
@ -65,17 +64,30 @@ func TestAuctionBidding(t *testing.T) {
{
"basic: auction doesn't exist",
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,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
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",
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),
someTime.Add(types.DefaultBidDuration),
buyer,
@ -85,7 +97,8 @@ func TestAuctionBidding(t *testing.T) {
{
"surplus: second bidder",
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),
someTime.Add(types.DefaultBidDuration),
secondBuyer,
@ -95,27 +108,52 @@ func TestAuctionBidding(t *testing.T) {
{
"surplus: invalid bid denom",
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.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),
buyer,
c("token2", 10),
c("token2", 100),
false,
},
{
"surplus: invalid bid (equal)",
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,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
c("token2", 100),
false,
},
{
"debt: normal",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("token1", 10), nil},
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
nil,
bidArgs{buyer, c("token1", 10)},
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
buyer,
@ -124,8 +162,9 @@ func TestAuctionBidding(t *testing.T) {
},
{
"debt: second bidder",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("token1", 10), secondBuyer},
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
[]bidArgs{{buyer, c("token1", 10)}},
bidArgs{secondBuyer, c("token1", 9)},
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
secondBuyer,
@ -134,28 +173,53 @@ func TestAuctionBidding(t *testing.T) {
},
{
"debt: invalid lot denom",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
bidArgs{buyer, c("badtoken", 10), nil},
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidLotDenom,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token1", 20),
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
},
{
"debt: invalid lot size (larger)",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token1", 21), nil},
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 21)},
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token1", 20),
types.DistantFuture,
supply.NewModuleAddress(modName),
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,
},
{
"collateral [forward]: normal",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 10), nil},
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("token2", 10)},
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
buyer,
@ -164,8 +228,9 @@ func TestAuctionBidding(t *testing.T) {
},
{
"collateral [forward]: second bidder",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 10), secondBuyer},
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 10)}},
bidArgs{secondBuyer, c("token2", 11)},
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
secondBuyer,
@ -174,18 +239,20 @@ func TestAuctionBidding(t *testing.T) {
},
{
"collateral [forward]: invalid bid denom",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("badtoken", 10), nil},
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidBidDenom,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
types.DistantFuture,
nil,
c("token2", 0),
false,
},
{
"collateral [forward]: invalid bid size (smaller)",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 0), nil}, // lot, bid
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 10)}},
bidArgs{buyer, c("token2", 9)},
types.CodeBidTooSmall,
someTime.Add(types.DefaultBidDuration),
buyer,
@ -193,19 +260,54 @@ func TestAuctionBidding(t *testing.T) {
false,
},
{
"collateral [forward]: invalid bid size (greater than max)",
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token2", 101), nil}, // lot, bid
types.CodeBidTooLarge,
"collateral [forward]: invalid bid size (equal)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
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),
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,
},
{
"collateral [reverse]: normal",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 15), nil},
auctionArgs{Collateral, modName, c("token1", 20), 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", 15)},
sdk.CodeType(0),
someTime.Add(types.DefaultBidDuration),
buyer,
@ -214,8 +316,9 @@ func TestAuctionBidding(t *testing.T) {
},
{
"collateral [reverse]: second bidder",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 15), secondBuyer},
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]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),
someTime.Add(types.DefaultBidDuration),
secondBuyer,
@ -224,28 +327,20 @@ func TestAuctionBidding(t *testing.T) {
},
{
"collateral [reverse]: invalid lot denom",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("badtoken", 15), nil},
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("badtoken", 15)},
types.CodeInvalidLotDenom,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
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)",
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
bidArgs{buyer, c("token1", 21), nil},
auctionArgs{Collateral, modName, c("token1", 20), 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", 21)},
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
@ -253,13 +348,25 @@ func TestAuctionBidding(t *testing.T) {
false,
},
{
"basic: closed auction",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
bidArgs{buyer, c("token2", 10), nil},
types.CodeAuctionHasExpired,
"collateral [reverse]: invalid lot size (equal)",
auctionArgs{Collateral, modName, c("token1", 20), 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", 20)},
types.CodeLotTooLarge,
someTime.Add(types.DefaultBidDuration),
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,
},
}
@ -283,6 +390,7 @@ func TestAuctionBidding(t *testing.T) {
)
ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper()
bank := tApp.GetBankKeeper()
// Start Auction
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)
case 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
// 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:
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)
if strings.Contains(tc.name, "closed") {
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
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
if tc.expectpass {
require.Nil(t, err)
// Get auction from store
auction, found := keeper.GetAuction(ctx, id)
// Check auction was found
newAuction, found := keeper.GetAuction(ctx, id)
require.True(t, found)
// Check auction values
require.Equal(t, modName, auction.GetInitiator())
require.Equal(t, tc.expectedBidder, auction.GetBidder())
require.Equal(t, tc.expectedBid, auction.GetBid())
require.Equal(t, tc.expectedEndTime, auction.GetEndTime())
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 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 {
// 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)
// 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
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}
// EndBlock module end-block
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
EndBlocker(ctx, am.keeper)
func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}

View File

@ -9,6 +9,9 @@
type Params struct {
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.
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 | sender | {sender address} |
## EndBlock
## BeginBlock
| Type | Attribute Key | Attribute Value |
|---------------|---------------|-----------------|

View File

@ -2,7 +2,10 @@
The auction module contains the following parameters:
| Key | Type | Example |
| ------------------ | ---------------------- | -----------|
| MaxAuctionDuration | string (time.Duration) | "48h0m0s" |
| BidDuration | string (time.Duration) | "3h0m0s" |
| Key | Type | Example | Description |
|---------------------|------------------------|------------------------|---------------------------------------------------------------------------------------|
| MaxAuctionDuration | string (time.Duration) | "48h0m0s" | |
| 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
var expiredAuctions []uint64

View File

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

View File

@ -21,9 +21,10 @@ const (
CodeInvalidLotDenom sdk.CodeType = 8
CodeBidTooSmall sdk.CodeType = 9
CodeBidTooLarge sdk.CodeType = 10
CodeLotTooLarge sdk.CodeType = 11
CodeCollateralAuctionIsInReversePhase sdk.CodeType = 12
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 13
CodeLotTooSmall sdk.CodeType = 11
CodeLotTooLarge sdk.CodeType = 12
CodeCollateralAuctionIsInReversePhase sdk.CodeType = 13
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 14
)
// 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))
}
// ErrBidTooSmall error for when bid is not greater than auction's last bid
func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, lastBid 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()))
// ErrBidTooSmall error for when bid is not greater than auction's min bid amount
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 min new bid amount %s", bid.String(), minBid.String()))
}
// 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()))
}
// ErrLotTooLarge error for when lot is not smaller than auction's last lot
func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, lastLot 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()))
// ErrLotToosmall error for when lot is less than zero
func ErrLotTooSmall(codespace sdk.CodespaceType, lot sdk.Coin, minLot sdk.Coin) sdk.Error {
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

View File

@ -17,11 +17,15 @@ const (
DefaultBidDuration time.Duration = 1 * time.Hour
)
// Parameter keys
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
KeyAuctionBidDuration = []byte("BidDuration")
KeyAuctionDuration = []byte("MaxAuctionDuration")
KeyBidDuration = []byte("BidDuration")
KeyMaxAuctionDuration = []byte("MaxAuctionDuration")
KeyIncrementSurplus = []byte("IncrementSurplus")
KeyIncrementDebt = []byte("IncrementDebt")
KeyIncrementCollateral = []byte("IncrementCollateral")
)
var _ subspace.ParamSet = &Params{}
@ -30,13 +34,19 @@ var _ subspace.ParamSet = &Params{}
type Params struct {
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.
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.
func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params {
func NewParams(maxAuctionDuration, bidDuration time.Duration, incrementSurplus, incrementDebt, incrementCollateral sdk.Dec) Params {
return Params{
MaxAuctionDuration: maxAuctionDuration,
BidDuration: bidDuration,
IncrementSurplus: incrementSurplus,
IncrementDebt: incrementDebt,
IncrementCollateral: incrementCollateral,
}
}
@ -45,6 +55,9 @@ func DefaultParams() Params {
return NewParams(
DefaultMaxAuctionDuration,
DefaultBidDuration,
DefaultIncrement,
DefaultIncrement,
DefaultIncrement,
)
}
@ -56,8 +69,11 @@ func ParamKeyTable() subspace.KeyTable {
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs.
func (p *Params) ParamSetPairs() subspace.ParamSetPairs {
return subspace.ParamSetPairs{
{Key: KeyAuctionBidDuration, Value: &p.BidDuration},
{Key: KeyAuctionDuration, Value: &p.MaxAuctionDuration},
{Key: KeyBidDuration, Value: &p.BidDuration},
{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 {
return fmt.Sprintf(`Auction Params:
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.
@ -86,5 +106,17 @@ func (p Params) Validate() error {
if p.BidDuration > p.MaxAuctionDuration {
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
}

View File

@ -1,9 +1,11 @@
package types
import (
"github.com/stretchr/testify/require"
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestParams_Validate(t *testing.T) {
@ -11,23 +13,89 @@ func TestParams_Validate(t *testing.T) {
}
testCases := []struct {
name string
MaxAuctionDuration time.Duration
BidDuration time.Duration
Params
expectErr bool
}{
{"normal", 24 * time.Hour, 1 * time.Hour, false},
{"negativeBid", 24 * time.Hour, -1 * time.Hour, true},
{"negativeAuction", -24 * time.Hour, 1 * time.Hour, true},
{"bid>auction", 1 * time.Hour, 24 * time.Hour, true},
{"zeros", 0, 0, false},
{
"normal",
DefaultParams(),
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 {
t.Run(tc.name, func(t *testing.T) {
p := Params{
MaxAuctionDuration: tc.MaxAuctionDuration,
BidDuration: tc.BidDuration,
}
err := p.Validate()
err := tc.Params.Validate()
if tc.expectErr {
require.Error(t, err)
} else {
@ -36,3 +104,5 @@ func TestParams_Validate(t *testing.T) {
})
}
}
func d(amount string) sdk.Dec { return sdk.MustNewDecFromStr(amount) }