From f11d3a0efd686a98a23e30db5a1608087f998f91 Mon Sep 17 00:00:00 2001 From: ze97286 Date: Thu, 26 Sep 2024 10:01:38 +0100 Subject: [PATCH] chore: reject pap on closed spot market + fix order id determinism --- core/execution/spot/market.go | 4 +- .../spot/protocol_automated_purchase.go | 5 +- .../engine_new_automated_purchase.go | 3 + .../engine_new_automated_purchase_test.go | 76 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/core/execution/spot/market.go b/core/execution/spot/market.go index d799d97547..cc7dad3a69 100644 --- a/core/execution/spot/market.go +++ b/core/execution/spot/market.go @@ -3488,7 +3488,7 @@ func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmiss quantumMultiplier) } -func (m *Market) enterAutomatedPurchaseAuction(ctx context.Context, orderSide types.Side, orderPrice *num.Uint, orderSize uint64, reference string, duration time.Duration) (string, error) { +func (m *Market) enterAutomatedPurchaseAuction(ctx context.Context, orderID string, orderSide types.Side, orderPrice *num.Uint, orderSize uint64, reference string, duration time.Duration) (string, error) { if !m.canTrade() { return "", fmt.Errorf(fmt.Sprintf("cannot trade in market %s", m.mkt.ID)) } @@ -3529,7 +3529,7 @@ func (m *Market) enterAutomatedPurchaseAuction(ctx context.Context, orderSide ty Type: types.OrderTypeLimit, Reference: reference, } - conf, err := m.SubmitOrder(ctx, os, types.NetworkParty, crypto.RandomHash()) + conf, err := m.SubmitOrder(ctx, os, types.NetworkParty, orderID) if err != nil { return "", err } diff --git a/core/execution/spot/protocol_automated_purchase.go b/core/execution/spot/protocol_automated_purchase.go index f2728164e0..531034ebcd 100644 --- a/core/execution/spot/protocol_automated_purchase.go +++ b/core/execution/spot/protocol_automated_purchase.go @@ -17,6 +17,7 @@ package spot import ( "context" + "encoding/hex" "sync" "time" @@ -27,6 +28,7 @@ import ( "code.vegaprotocol.io/vega/core/execution/common" "code.vegaprotocol.io/vega/core/products" "code.vegaprotocol.io/vega/core/types" + "code.vegaprotocol.io/vega/libs/crypto" "code.vegaprotocol.io/vega/libs/num" "code.vegaprotocol.io/vega/logging" snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" @@ -315,7 +317,8 @@ func (m *Market) papAuctionSchedule(ctx context.Context, data dscommon.Data) err } orderPriceInMarket := m.priceToMarketPrecision(orderPrice) - orderID, err := m.enterAutomatedPurchaseAuction(ctx, m.pap.side, orderPriceInMarket, orderSize, m.pap.ID, m.pap.config.AuctionDuration) + orderID := hex.EncodeToString(crypto.Hash([]byte(m.pap.ID))) + orderID, err := m.enterAutomatedPurchaseAuction(ctx, orderID, m.pap.side, orderPriceInMarket, orderSize, m.pap.ID, m.pap.config.AuctionDuration) // if there was no error save the order id as an indication that we're in an auction with active pap order if err == nil { m.pap.activeOrder = orderID diff --git a/core/governance/engine_new_automated_purchase.go b/core/governance/engine_new_automated_purchase.go index 9e7e7ee599..90190a025e 100644 --- a/core/governance/engine_new_automated_purchase.go +++ b/core/governance/engine_new_automated_purchase.go @@ -37,6 +37,9 @@ func (e *Engine) validateNewProtocolAutomatedPurchaseConfiguration(automatedPurc if automatedPurchase.Changes.From != spot.BaseAsset && automatedPurchase.Changes.From != spot.QuoteAsset { return types.ProposalErrorInvalidMarket, fmt.Errorf("mismatch between asset for automated purchase and the spot market configuration - asset is not one of base/quote assets of the market") } + if mkt.State == types.MarketStateClosed || mkt.State == types.MarketStateCancelled || mkt.State == types.MarketStateRejected || mkt.State == types.MarketStateTradingTerminated || mkt.State == types.MarketStateSettled { + return types.ProposalErrorInvalidMarket, fmt.Errorf("market for automated purchase must be active") + } if papConfigured, _ := e.markets.MarketHasActivePAP(automatedPurchase.Changes.MarketID); papConfigured { return types.ProposalErrorInvalidMarket, fmt.Errorf("market already has an active protocol automated purchase program") } diff --git a/core/governance/engine_new_automated_purchase_test.go b/core/governance/engine_new_automated_purchase_test.go index f42509bcfd..cdd69db44d 100644 --- a/core/governance/engine_new_automated_purchase_test.go +++ b/core/governance/engine_new_automated_purchase_test.go @@ -439,3 +439,79 @@ func testSubmittingProposalForNewProtocolAutomatedPurchaseNotSpotMarketFails(t * require.Error(t, err) require.Equal(t, "market for automated purchase must be a spot market", err.Error()) } + +func testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketFailes(t *testing.T, state types.MarketState) { + now := time.Now() + ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64()) + eng := getTestEngine(t, now) + + // setup + eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h") + eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h") + eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000") + + eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{ + State: state, + TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{ + RiskModel: &vega.TradableInstrument_SimpleRiskModel{ + SimpleRiskModel: &vega.SimpleRiskModel{ + Params: &vega.SimpleModelParams{}, + }, + }, + Instrument: &vega.Instrument{ + Product: &vega.Instrument_Spot{ + Spot: &vega.Spot{ + BaseAsset: "base", + QuoteAsset: "quote", + }, + }, + Metadata: &vega.InstrumentMetadata{}, + }, + }), + }, true).AnyTimes() + eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes() + eng.markets.EXPECT().MarketHasActivePAP(gomock.Any()).Return(false, nil).AnyTimes() + + // given + proposer := vgrand.RandomStr(5) + proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{ + ExpiryTimestamp: now.Add(4 * 48 * time.Hour), + From: "base", + FromAccountType: types.AccountTypeBuyBackFees, + ToAccountType: types.AccountTypeBuyBackFees, + MarketID: crypto.RandomHash(), + PriceOracle: &vega.DataSourceDefinition{}, + PriceOracleBinding: &vega.SpecBindingForCompositePrice{ + PriceSourceProperty: "oracle.price", + }, + OracleOffsetFactor: num.DecimalFromFloat(0.1), + AuctionSchedule: &vega.DataSourceDefinition{}, + AuctionVolumeSnapshotSchedule: &vega.DataSourceDefinition{}, + AutomatedPurchaseSpecBinding: &vega.DataSourceSpecToAutomatedPurchaseBinding{}, + AuctionDuration: time.Hour, + MinimumAuctionSize: num.NewUint(1000), + MaximumAuctionSize: num.NewUint(2000), + }) + + // setup + eng.ensureTokenBalanceForParty(t, proposer, 1000) + + // expect + eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket) + + // when + _, err := eng.submitProposal(t, proposal) + + // then + require.Error(t, err) + require.Equal(t, "market for automated purchase must be active", err.Error()) +} + +func TestSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketWithStateFails(t *testing.T) { + stoppedStates := []types.MarketState{types.MarketStateCancelled, types.MarketStateClosed, types.MarketStateRejected, types.MarketStateSettled, types.MarketStateTradingTerminated} + for _, state := range stoppedStates { + testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketFailes(t, state) + } + +}