From edce4db87f325fcc78cd015d06da5ff8ca22d0e7 Mon Sep 17 00:00:00 2001 From: Rene Zander Date: Wed, 3 Jun 2026 06:23:23 +0000 Subject: [PATCH] fix(market): enforce OrderMaxBids with >= instead of > CreateBid compared the existing bid count against OrderMaxBids using `>`, which let one extra bid through and allowed OrderMaxBids+1 bids per order. BidCountForOrder counts Open, Active and Closed bids, so the count reaches OrderMaxBids exactly at the cap and the check must reject at that point. Switch the comparison to `>=` and add a regression test that seeds an order to its cap and asserts a further bid is rejected with "too many existing bids". Closes akash-network/support#413 Co-Authored-By: Claude Opus 4.8 (1M context) --- x/market/handler/handler_test.go | 39 ++++++++++++++++++++++++++++++++ x/market/handler/server.go | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/x/market/handler/handler_test.go b/x/market/handler/handler_test.go index fb72a2603..5c8d27696 100644 --- a/x/market/handler/handler_test.go +++ b/x/market/handler/handler_test.go @@ -915,6 +915,45 @@ func TestCreateBidValid(t *testing.T) { require.True(t, found) } +// TestCreateBidExceedsOrderMaxBids is a regression test for the OrderMaxBids +// off-by-one (akash-network/support#413). The original check used `>`, which +// allowed OrderMaxBids+1 bids per order. With the cap set to 1 and one bid +// already present, a further bid must be rejected. +func TestCreateBidExceedsOrderMaxBids(t *testing.T) { + suite := setupTestSuite(t) + + order, gspec := suite.createOrder(testutil.Resources(t, testutil.WithDenom("uact"))) + + params, err := suite.MarketKeeper().GetParams(suite.Context()) + require.NoError(t, err) + params.OrderMaxBids = 1 + require.NoError(t, suite.MarketKeeper().SetParams(suite.Context(), params)) + + // Seed the single permitted bid directly via the keeper so the order is at + // its cap. The OrderMaxBids check runs before any provider/escrow lookup, so + // no provider registration or bank mocks are required here. + roffer := mvbeta.ResourceOfferFromRU(gspec.Resources) + _, err = suite.MarketKeeper().CreateBid(suite.Context(), mv1.MakeBidID(order.ID, testutil.AccAddress(t)), order.Price(), roffer, nil) + require.NoError(t, err) + require.Equal(t, uint32(1), suite.MarketKeeper().BidCountForOrder(suite.Context(), order.ID)) + + // With the cap reached, a further bid must be rejected. + msg := &mvbeta.MsgCreateBid{ + ID: mv1.MakeBidID(order.ID, testutil.AccAddress(t)), + Price: sdk.NewDecCoin(sdkutil.DenomUact, sdkmath.NewInt(1)), + Deposit: deposit.Deposit{ + Amount: mvbeta.DefaultBidMinDepositACT, + Sources: deposit.Sources{deposit.SourceBalance}, + }, + } + + res, err := suite.handler(suite.Context(), msg) + require.Nil(t, res) + require.Error(t, err) + require.ErrorIs(t, err, mv1.ErrInvalidBid) + require.Contains(t, err.Error(), "too many existing bids") +} + func TestCreateBidInvalidPrice(t *testing.T) { suite := setupTestSuite(t) suite.PrepareMocks(func(ts *state.TestSuite) { diff --git a/x/market/handler/server.go b/x/market/handler/server.go index 935b7dd78..5764d3b0f 100644 --- a/x/market/handler/server.go +++ b/x/market/handler/server.go @@ -42,7 +42,7 @@ func (ms msgServer) CreateBid(goCtx context.Context, msg *mvbeta.MsgCreateBid) ( return nil, fmt.Errorf("%w: minimum:%v received:%v", mv1.ErrInvalidDeposit, sdk.NewCoin(msg.Deposit.Amount.Denom, minDeposit), msg.Deposit) } - if ms.keepers.Market.BidCountForOrder(ctx, msg.ID.OrderID()) > params.OrderMaxBids { + if ms.keepers.Market.BidCountForOrder(ctx, msg.ID.OrderID()) >= params.OrderMaxBids { return nil, fmt.Errorf("%w: too many existing bids (%v)", mv1.ErrInvalidBid, params.OrderMaxBids) }