From fdfd12a81d51b4c8cf0c6b7c1a10e9a547aad37d Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Thu, 16 Mar 2023 19:40:09 -0400 Subject: [PATCH] [ENG-585]: Setting min bid increment in ante (#28) --- mempool/mempool.go | 10 ++++++++++ x/auction/ante/ante.go | 21 +++++++++++++++++++-- x/auction/keeper/auction.go | 24 +++++++++++++++++------- x/auction/keeper/auction_test.go | 24 +++++++++++++++++++++++- x/auction/keeper/keeper_test.go | 6 +++++- 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 5a41a9b..2b06b60 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -158,6 +158,16 @@ func (am *AuctionMempool) RemoveWithoutRefTx(tx sdk.Tx) error { return nil } +// GetTopAuctionTx returns the highest bidding transaction in the auction mempool. +func (am *AuctionMempool) GetTopAuctionTx(ctx context.Context) sdk.Tx { + iterator := am.auctionIndex.Select(ctx, nil) + if iterator == nil { + return nil + } + + return iterator.Tx() +} + // AuctionBidSelect returns an iterator over auction bids transactions only. func (am *AuctionMempool) AuctionBidSelect(ctx context.Context) sdkmempool.Iterator { return am.auctionIndex.Select(ctx, nil) diff --git a/x/auction/ante/ante.go b/x/auction/ante/ante.go index 2db0029..7aaa958 100644 --- a/x/auction/ante/ante.go +++ b/x/auction/ante/ante.go @@ -12,12 +12,14 @@ var _ sdk.AnteDecorator = AuctionDecorator{} type AuctionDecorator struct { auctionKeeper keeper.Keeper txDecoder sdk.TxDecoder + mempool *mempool.AuctionMempool } -func NewAuctionDecorator(ak keeper.Keeper, txDecoder sdk.TxDecoder) AuctionDecorator { +func NewAuctionDecorator(ak keeper.Keeper, txDecoder sdk.TxDecoder, mempool *mempool.AuctionMempool) AuctionDecorator { return AuctionDecorator{ auctionKeeper: ak, txDecoder: txDecoder, + mempool: mempool, } } @@ -46,10 +48,25 @@ func (ad AuctionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, transactions[i] = decodedTx } - if err := ad.auctionKeeper.ValidateAuctionMsg(ctx, bidder, auctionMsg.Bid, transactions); err != nil { + highestBid, err := ad.GetHighestAuctionBid(ctx) + if err != nil { + return ctx, errors.Wrap(err, "failed to get highest auction bid") + } + + if err := ad.auctionKeeper.ValidateAuctionMsg(ctx, bidder, auctionMsg.Bid, highestBid, transactions); err != nil { return ctx, errors.Wrap(err, "failed to validate auction bid") } } return next(ctx, tx, simulate) } + +// GetHighestAuctionBid returns the highest auction bid if one exists. +func (ad AuctionDecorator) GetHighestAuctionBid(ctx sdk.Context) (sdk.Coins, error) { + auctionTx := ad.mempool.GetTopAuctionTx(ctx) + if auctionTx == nil { + return sdk.NewCoins(), nil + } + + return auctionTx.(*mempool.WrappedBidTx).GetBid(), nil +} diff --git a/x/auction/keeper/auction.go b/x/auction/keeper/auction.go index c093d1d..ee8fcd4 100644 --- a/x/auction/keeper/auction.go +++ b/x/auction/keeper/auction.go @@ -6,10 +6,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// ValidateAuctionMsg validates that the MsgAuctionBid is valid. It checks that the bidder has sufficient funds to bid the -// amount specified in the message, that the bundle size is not greater than the max bundle size, and that the bundle -// transactions are valid. -func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid sdk.Coins, transactions []sdk.Tx) error { +// ValidateAuctionMsg validates that the MsgAuctionBid can be included in the auction. +func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid, highestBid sdk.Coins, transactions []sdk.Tx) error { // Validate the bundle size. maxBundleSize, err := k.GetMaxBundleSize(ctx) if err != nil { @@ -21,7 +19,7 @@ func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid s } // Validate the bid amount. - if err := k.ValidateAuctionBid(ctx, bidder, bid); err != nil { + if err := k.ValidateAuctionBid(ctx, bidder, bid, highestBid); err != nil { return err } @@ -40,8 +38,20 @@ func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid s return nil } -// ValidateAuctionBid validates that the bidder has sufficient funds to participate in the auction. -func (k Keeper) ValidateAuctionBid(ctx sdk.Context, bidder sdk.AccAddress, bid sdk.Coins) error { +// ValidateAuctionBid validates that the bidder has sufficient funds to participate in the auction and that the bid amount +// is sufficiently high enough. +func (k Keeper) ValidateAuctionBid(ctx sdk.Context, bidder sdk.AccAddress, bid, highestBid sdk.Coins) error { + // Ensure the bid is greater than the highest bid + min bid increment. + minBidIncrement, err := k.GetMinBidIncrement(ctx) + if err != nil { + return err + } + + minBid := highestBid.Add(minBidIncrement...) + if !bid.IsAllGTE(minBid) { + return fmt.Errorf("bid amount (%s) is less than the highest bid (%s) + min bid increment (%s)", bid, highestBid, minBidIncrement) + } + // Get the bid floor. reserveFee, err := k.GetReserveFee(ctx) if err != nil { diff --git a/x/auction/keeper/auction_test.go b/x/auction/keeper/auction_test.go index 639487d..70d1d76 100644 --- a/x/auction/keeper/auction_test.go +++ b/x/auction/keeper/auction_test.go @@ -24,8 +24,12 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { maxBundleSize uint32 = 10 reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000))) minBuyInFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000))) + minBidIncrement = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000))) escrowAddress = sdk.AccAddress([]byte("escrow")) frontRunningProtection = true + + // mempool variables + highestBid = sdk.NewCoins() ) rnd := rand.New(rand.NewSource(time.Now().Unix())) @@ -123,6 +127,23 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { }, true, }, + { + "invalid bundle that does not outbid the highest bid", + func() { + accounts = []Account{bidder, bidder, bidder} + highestBid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(500))) + bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(500))) + }, + false, + }, + { + "valid bundle that outbids the highest bid", + func() { + highestBid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(500))) + bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1500))) + }, + true, + }, } for _, tc := range cases { @@ -148,6 +169,7 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { MinBuyInFee: minBuyInFee, EscrowAccountAddress: escrowAddress.String(), FrontRunningProtection: frontRunningProtection, + MinBidIncrement: minBidIncrement, } suite.auctionKeeper.SetParams(suite.ctx, params) @@ -159,7 +181,7 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { bundle = append(bundle, tx) } - err := suite.auctionKeeper.ValidateAuctionMsg(suite.ctx, bidder.Address, bid, bundle) + err := suite.auctionKeeper.ValidateAuctionMsg(suite.ctx, bidder.Address, bid, highestBid, bundle) if tc.pass { suite.Require().NoError(err) } else { diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 45ad2ed..af76edf 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/golang/mock/gomock" + "github.com/skip-mev/pob/mempool" "github.com/skip-mev/pob/x/auction/ante" "github.com/skip-mev/pob/x/auction/keeper" "github.com/skip-mev/pob/x/auction/types" @@ -37,6 +38,8 @@ type IntegrationTestSuite struct { msgServer types.MsgServer key *storetypes.KVStoreKey authorityAccount sdk.AccAddress + + mempool *mempool.AuctionMempool } func TestKeeperTestSuite(t *testing.T) { @@ -60,7 +63,8 @@ func (suite *IntegrationTestSuite) SetupTest() { err := suite.auctionKeeper.SetParams(suite.ctx, types.DefaultParams()) suite.Require().NoError(err) - suite.AuctionDecorator = ante.NewAuctionDecorator(suite.auctionKeeper, suite.encCfg.TxConfig.TxDecoder()) + suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), 0) + suite.AuctionDecorator = ante.NewAuctionDecorator(suite.auctionKeeper, suite.encCfg.TxConfig.TxDecoder(), suite.mempool) suite.msgServer = keeper.NewMsgServerImpl(suite.auctionKeeper) }