code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_new_automated_purchase_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package governance_test
    17  
    18  import (
    19  	"testing"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/datasource"
    23  	"code.vegaprotocol.io/vega/core/datasource/definition"
    24  	"code.vegaprotocol.io/vega/core/netparams"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	"code.vegaprotocol.io/vega/libs/crypto"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    29  	vgtest "code.vegaprotocol.io/vega/libs/test"
    30  	"code.vegaprotocol.io/vega/protos/vega"
    31  	v1 "code.vegaprotocol.io/vega/protos/vega/data/v1"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func defaultPriceOracle() *datasource.Spec {
    38  	dp := uint64(5)
    39  	spec := &vega.DataSourceSpec{
    40  		Data: &vega.DataSourceDefinition{
    41  			SourceType: vega.DataSourceDefinition{
    42  				SourceType: &vega.DataSourceDefinition_External{
    43  					External: &vega.DataSourceDefinitionExternal{
    44  						SourceType: &vega.DataSourceDefinitionExternal_Oracle{
    45  							Oracle: &vega.DataSourceSpecConfiguration{
    46  								Signers: []*v1.Signer{
    47  									{
    48  										Signer: &v1.Signer_PubKey{
    49  											PubKey: &v1.PubKey{
    50  												Key: "0xiBADC0FFEE0DDF00D",
    51  											},
    52  										},
    53  									},
    54  								},
    55  								Filters: []*v1.Filter{
    56  									{
    57  										Key: &v1.PropertyKey{
    58  											Name:                "oracle.price",
    59  											Type:                v1.PropertyKey_TYPE_INTEGER,
    60  											NumberDecimalPlaces: &dp,
    61  										},
    62  										Conditions: []*v1.Condition{
    63  											{
    64  												Operator: v1.Condition_OPERATOR_UNSPECIFIED,
    65  											},
    66  										},
    67  									},
    68  								},
    69  							},
    70  						},
    71  					},
    72  				},
    73  			}.SourceType,
    74  		},
    75  	}
    76  	specDef, _ := definition.FromProto(spec.Data, nil)
    77  	return datasource.SpecFromDefinition(*definition.NewWith(specDef))
    78  }
    79  
    80  func defaultTimeTrigger() definition.Definition {
    81  	spec := &vega.DataSourceSpec{
    82  		Data: &vega.DataSourceDefinition{
    83  			SourceType: vega.DataSourceDefinition{
    84  				SourceType: &vega.DataSourceDefinition_Internal{
    85  					Internal: &vega.DataSourceDefinitionInternal{
    86  						SourceType: &vega.DataSourceDefinitionInternal_TimeTrigger{
    87  							TimeTrigger: &vega.DataSourceSpecConfigurationTimeTrigger{
    88  								Triggers: []*v1.InternalTimeTrigger{
    89  									{
    90  										Every: 10,
    91  									},
    92  								},
    93  							},
    94  						},
    95  					},
    96  				},
    97  			}.SourceType,
    98  		},
    99  	}
   100  
   101  	specDef, _ := definition.FromProto(spec.Data, nil)
   102  	return datasource.SpecFromDefinition(*definition.NewWith(specDef)).GetDefinition()
   103  }
   104  
   105  func TestProposalForNewProtocolAutomatedPurchase(t *testing.T) {
   106  	t.Run("Submitting a proposal for new automated purchase succeeds", testSubmittingProposalForNewProtocolAutomatedPurchaseSucceeds)
   107  	t.Run("Submitting a proposal for new automated purchase with invalid asset fails", testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidAssetFails)
   108  	t.Run("Submitting a proposal for new automated purchase with invalid market fails", testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidMarketFails)
   109  	t.Run("Submitting a proposal for new automated purchase with a market that is not a spot market fails", testSubmittingProposalForNewProtocolAutomatedPurchaseNotSpotMarketFails)
   110  	t.Run("Submitting a proposal for new automated purchase with invalid asset for spot market fails", testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidAssetForMarketFails)
   111  	t.Run("Submitting a proposal for new automated purchase to a stopped market fails", testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketWithStateFails)
   112  	t.Run("Submitting a proposal for new automated purchase to a market which already has an active pap fails", testSubmittingPAPToMarketWithActivePAPFails)
   113  }
   114  
   115  func testSubmittingProposalForNewProtocolAutomatedPurchaseSucceeds(t *testing.T) {
   116  	now := time.Now()
   117  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   118  	eng := getTestEngine(t, now)
   119  
   120  	// setup
   121  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   122  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   123  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   124  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   125  
   126  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   127  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   128  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   129  				SimpleRiskModel: &vega.SimpleRiskModel{
   130  					Params: &vega.SimpleModelParams{},
   131  				},
   132  			},
   133  			Instrument: &vega.Instrument{
   134  				Product: &vega.Instrument_Spot{
   135  					Spot: &vega.Spot{
   136  						BaseAsset:  "base",
   137  						QuoteAsset: "quote",
   138  					},
   139  				},
   140  				Metadata: &vega.InstrumentMetadata{},
   141  			},
   142  		}),
   143  	}, true).AnyTimes()
   144  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   145  	eng.markets.EXPECT().MarketHasActivePAP(gomock.Any()).Return(false, nil).AnyTimes()
   146  
   147  	// given
   148  	proposer := vgrand.RandomStr(5)
   149  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   150  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   151  		From:            "base",
   152  		FromAccountType: types.AccountTypeBuyBackFees,
   153  		ToAccountType:   types.AccountTypeBuyBackFees,
   154  		MarketID:        crypto.RandomHash(),
   155  		PriceOracle:     defaultPriceOracle(),
   156  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   157  			PriceSourceProperty: "oracle.price",
   158  		},
   159  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   160  		AuctionSchedule:               defaultTimeTrigger(),
   161  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   162  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   163  		AuctionDuration:               time.Hour,
   164  		MinimumAuctionSize:            num.NewUint(1000),
   165  		MaximumAuctionSize:            num.NewUint(2000),
   166  	})
   167  
   168  	// setup
   169  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   170  
   171  	// expect
   172  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   173  
   174  	// when
   175  	toSubmit, err := eng.submitProposal(t, proposal)
   176  
   177  	// then
   178  	require.NoError(t, err)
   179  	require.NotNil(t, toSubmit)
   180  }
   181  
   182  func setupPAP(t *testing.T) (*tstEngine, types.Proposal) {
   183  	t.Helper()
   184  	now := time.Now()
   185  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   186  	eng := getTestEngine(t, now)
   187  
   188  	// setup
   189  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   190  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   191  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   192  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   193  
   194  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   195  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   196  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   197  				SimpleRiskModel: &vega.SimpleRiskModel{
   198  					Params: &vega.SimpleModelParams{},
   199  				},
   200  			},
   201  			Instrument: &vega.Instrument{
   202  				Product: &vega.Instrument_Spot{
   203  					Spot: &vega.Spot{
   204  						BaseAsset:  "base",
   205  						QuoteAsset: "quote",
   206  					},
   207  				},
   208  				Metadata: &vega.InstrumentMetadata{},
   209  			},
   210  		}),
   211  	}, true).AnyTimes()
   212  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   213  
   214  	// given
   215  	proposer := vgrand.RandomStr(5)
   216  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   217  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   218  		From:            "base",
   219  		FromAccountType: types.AccountTypeBuyBackFees,
   220  		ToAccountType:   types.AccountTypeBuyBackFees,
   221  		MarketID:        crypto.RandomHash(),
   222  		PriceOracle:     defaultPriceOracle(),
   223  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   224  			PriceSourceProperty: "oracle.price",
   225  		},
   226  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   227  		AuctionSchedule:               defaultTimeTrigger(),
   228  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   229  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   230  		AuctionDuration:               time.Hour,
   231  		MinimumAuctionSize:            num.NewUint(1000),
   232  		MaximumAuctionSize:            num.NewUint(2000),
   233  	})
   234  	return eng, proposal
   235  }
   236  
   237  func testSubmittingPAPToMarketWithActivePAPFails(t *testing.T) {
   238  	eng, proposal := setupPAP(t)
   239  	// setup
   240  	eng.markets.EXPECT().MarketHasActivePAP(gomock.Any()).Return(false, nil).Times(1)
   241  	eng.ensureTokenBalanceForParty(t, proposal.Party, 1000)
   242  
   243  	// expect
   244  	eng.expectOpenProposalEvent(t, proposal.Party, proposal.ID)
   245  
   246  	// when
   247  	toSubmit, err := eng.submitProposal(t, proposal)
   248  
   249  	// then
   250  	require.NoError(t, err)
   251  	require.NotNil(t, toSubmit)
   252  
   253  	// now resubmit to the same market when this one is already active
   254  	proposal.ID = crypto.RandomHash()
   255  	eng.markets.EXPECT().MarketHasActivePAP(gomock.Any()).Return(true, nil).Times(1)
   256  	eng.ensureTokenBalanceForParty(t, proposal.Party, 1000)
   257  	// expect
   258  	eng.expectRejectedProposalEvent(t, proposal.Party, proposal.ID, types.ProposalErrorInvalidMarket)
   259  
   260  	// when
   261  	_, err = eng.submitProposal(t, proposal)
   262  
   263  	// then
   264  	require.Equal(t, "market already has an active protocol automated purchase program", err.Error())
   265  }
   266  
   267  func testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidAssetFails(t *testing.T) {
   268  	now := time.Now()
   269  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   270  	eng := getTestEngine(t, now)
   271  
   272  	// setup
   273  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   274  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   275  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   276  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   277  
   278  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   279  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   280  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   281  				SimpleRiskModel: &vega.SimpleRiskModel{
   282  					Params: &vega.SimpleModelParams{},
   283  				},
   284  			},
   285  			Instrument: &vega.Instrument{
   286  				Product: &vega.Instrument_Spot{
   287  					Spot: &vega.Spot{
   288  						BaseAsset:  "base",
   289  						QuoteAsset: "quote",
   290  					},
   291  				},
   292  				Metadata: &vega.InstrumentMetadata{},
   293  			},
   294  		}),
   295  	}, true).AnyTimes()
   296  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(false).AnyTimes()
   297  
   298  	// given
   299  	proposer := vgrand.RandomStr(5)
   300  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   301  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   302  		From:            "base",
   303  		FromAccountType: types.AccountTypeBuyBackFees,
   304  		ToAccountType:   types.AccountTypeBuyBackFees,
   305  		MarketID:        crypto.RandomHash(),
   306  		PriceOracle:     defaultPriceOracle(),
   307  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   308  			PriceSourceProperty: "oracle.price",
   309  		},
   310  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   311  		AuctionSchedule:               defaultTimeTrigger(),
   312  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   313  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   314  		AuctionDuration:               time.Hour,
   315  		MinimumAuctionSize:            num.NewUint(1000),
   316  		MaximumAuctionSize:            num.NewUint(2000),
   317  	})
   318  
   319  	// setup
   320  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   321  
   322  	// expect
   323  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidAsset)
   324  
   325  	// when
   326  	_, err := eng.submitProposal(t, proposal)
   327  
   328  	// then
   329  	require.Error(t, err)
   330  	require.Equal(t, "asset does not exist", err.Error())
   331  }
   332  
   333  func testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidMarketFails(t *testing.T) {
   334  	now := time.Now()
   335  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   336  	eng := getTestEngine(t, now)
   337  
   338  	// setup
   339  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   340  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   341  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   342  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   343  
   344  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{}, false).AnyTimes()
   345  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   346  
   347  	// given
   348  	proposer := vgrand.RandomStr(5)
   349  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   350  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   351  		From:            "base",
   352  		FromAccountType: types.AccountTypeBuyBackFees,
   353  		ToAccountType:   types.AccountTypeBuyBackFees,
   354  		MarketID:        crypto.RandomHash(),
   355  		PriceOracle:     defaultPriceOracle(),
   356  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   357  			PriceSourceProperty: "oracle.price",
   358  		},
   359  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   360  		AuctionSchedule:               defaultTimeTrigger(),
   361  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   362  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   363  		AuctionDuration:               time.Hour,
   364  		MinimumAuctionSize:            num.NewUint(1000),
   365  		MaximumAuctionSize:            num.NewUint(2000),
   366  	})
   367  
   368  	// setup
   369  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   370  
   371  	// expect
   372  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   373  
   374  	// when
   375  	_, err := eng.submitProposal(t, proposal)
   376  
   377  	// then
   378  	require.Error(t, err)
   379  	require.Equal(t, "market does not exist", err.Error())
   380  }
   381  
   382  func testSubmittingProposalForNewProtocolAutomatedPurchaseInvalidAssetForMarketFails(t *testing.T) {
   383  	now := time.Now()
   384  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   385  	eng := getTestEngine(t, now)
   386  
   387  	// setup
   388  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   389  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   390  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   391  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   392  
   393  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   394  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   395  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   396  				SimpleRiskModel: &vega.SimpleRiskModel{
   397  					Params: &vega.SimpleModelParams{},
   398  				},
   399  			},
   400  			Instrument: &vega.Instrument{
   401  				Product: &vega.Instrument_Spot{
   402  					Spot: &vega.Spot{
   403  						BaseAsset:  "base",
   404  						QuoteAsset: "quote",
   405  					},
   406  				},
   407  				Metadata: &vega.InstrumentMetadata{},
   408  			},
   409  		}),
   410  	}, true).AnyTimes()
   411  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   412  
   413  	// given
   414  	proposer := vgrand.RandomStr(5)
   415  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   416  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   417  		From:            "neither_base_nor_quote",
   418  		FromAccountType: types.AccountTypeBuyBackFees,
   419  		ToAccountType:   types.AccountTypeBuyBackFees,
   420  		MarketID:        crypto.RandomHash(),
   421  		PriceOracle:     defaultPriceOracle(),
   422  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   423  			PriceSourceProperty: "oracle.price",
   424  		},
   425  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   426  		AuctionSchedule:               defaultTimeTrigger(),
   427  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   428  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   429  		AuctionDuration:               time.Hour,
   430  		MinimumAuctionSize:            num.NewUint(1000),
   431  		MaximumAuctionSize:            num.NewUint(2000),
   432  	})
   433  
   434  	// setup
   435  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   436  
   437  	// expect
   438  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   439  
   440  	// when
   441  	_, err := eng.submitProposal(t, proposal)
   442  
   443  	// then
   444  	require.Error(t, err)
   445  	require.Equal(t, "mismatch between asset for automated purchase and the spot market configuration - asset is not one of base/quote assets of the market", err.Error())
   446  }
   447  
   448  func testSubmittingProposalForNewProtocolAutomatedPurchaseNotSpotMarketFails(t *testing.T) {
   449  	now := time.Now()
   450  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   451  	eng := getTestEngine(t, now)
   452  
   453  	// setup
   454  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   455  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   456  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   457  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   458  
   459  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   460  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   461  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   462  				SimpleRiskModel: &vega.SimpleRiskModel{
   463  					Params: &vega.SimpleModelParams{},
   464  				},
   465  			},
   466  			Instrument: &vega.Instrument{
   467  				Product: &vega.Instrument_Future{
   468  					Future: &vega.Future{
   469  						SettlementAsset:                     "some_future",
   470  						DataSourceSpecForSettlementData:     &vega.DataSourceSpec{},
   471  						DataSourceSpecForTradingTermination: &vega.DataSourceSpec{},
   472  						DataSourceSpecBinding:               &vega.DataSourceSpecToFutureBinding{},
   473  					},
   474  				},
   475  				Metadata: &vega.InstrumentMetadata{},
   476  			},
   477  		}),
   478  	}, true).AnyTimes()
   479  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   480  
   481  	// given
   482  	proposer := vgrand.RandomStr(5)
   483  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   484  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   485  		From:            "neither_base_nor_quote",
   486  		FromAccountType: types.AccountTypeBuyBackFees,
   487  		ToAccountType:   types.AccountTypeBuyBackFees,
   488  		MarketID:        crypto.RandomHash(),
   489  		PriceOracle:     defaultPriceOracle(),
   490  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   491  			PriceSourceProperty: "oracle.price",
   492  		},
   493  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   494  		AuctionSchedule:               defaultTimeTrigger(),
   495  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   496  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   497  		AuctionDuration:               time.Hour,
   498  		MinimumAuctionSize:            num.NewUint(1000),
   499  		MaximumAuctionSize:            num.NewUint(2000),
   500  	})
   501  
   502  	// setup
   503  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   504  
   505  	// expect
   506  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   507  
   508  	// when
   509  	_, err := eng.submitProposal(t, proposal)
   510  
   511  	// then
   512  	require.Error(t, err)
   513  	require.Equal(t, "market for automated purchase must be a spot market", err.Error())
   514  }
   515  
   516  func testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketFailes(t *testing.T, state types.MarketState) {
   517  	t.Helper()
   518  	now := time.Now()
   519  	ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64())
   520  	eng := getTestEngine(t, now)
   521  
   522  	// setup
   523  	eng.broker.EXPECT().Send(gomock.Any()).Times(3)
   524  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinClose, "48h")
   525  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinEnact, "48h")
   526  	eng.netp.Update(ctx, netparams.GovernanceProposalAutomatedPurchaseConfigMinProposerBalance, "1000")
   527  
   528  	eng.markets.EXPECT().GetMarket(gomock.Any(), gomock.Any()).Return(types.Market{
   529  		State: state,
   530  		TradableInstrument: types.TradableInstrumentFromProto(&vega.TradableInstrument{
   531  			RiskModel: &vega.TradableInstrument_SimpleRiskModel{
   532  				SimpleRiskModel: &vega.SimpleRiskModel{
   533  					Params: &vega.SimpleModelParams{},
   534  				},
   535  			},
   536  			Instrument: &vega.Instrument{
   537  				Product: &vega.Instrument_Spot{
   538  					Spot: &vega.Spot{
   539  						BaseAsset:  "base",
   540  						QuoteAsset: "quote",
   541  					},
   542  				},
   543  				Metadata: &vega.InstrumentMetadata{},
   544  			},
   545  		}),
   546  	}, true).AnyTimes()
   547  	eng.assets.EXPECT().IsEnabled(gomock.Any()).Return(true).AnyTimes()
   548  	eng.markets.EXPECT().MarketHasActivePAP(gomock.Any()).Return(false, nil).AnyTimes()
   549  
   550  	// given
   551  	proposer := vgrand.RandomStr(5)
   552  	proposal := eng.newProposalForNewProtocolAutomatedPurchase(proposer, now, &types.NewProtocolAutomatedPurchaseChanges{
   553  		ExpiryTimestamp: now.Add(4 * 48 * time.Hour),
   554  		From:            "base",
   555  		FromAccountType: types.AccountTypeBuyBackFees,
   556  		ToAccountType:   types.AccountTypeBuyBackFees,
   557  		MarketID:        crypto.RandomHash(),
   558  		PriceOracle:     defaultPriceOracle(),
   559  		PriceOracleBinding: &vega.SpecBindingForCompositePrice{
   560  			PriceSourceProperty: "oracle.price",
   561  		},
   562  		OracleOffsetFactor:            num.DecimalFromFloat(0.1),
   563  		AuctionSchedule:               defaultTimeTrigger(),
   564  		AuctionVolumeSnapshotSchedule: defaultTimeTrigger(),
   565  		AutomatedPurchaseSpecBinding:  &vega.DataSourceSpecToAutomatedPurchaseBinding{},
   566  		AuctionDuration:               time.Hour,
   567  		MinimumAuctionSize:            num.NewUint(1000),
   568  		MaximumAuctionSize:            num.NewUint(2000),
   569  	})
   570  
   571  	// setup
   572  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   573  
   574  	// expect
   575  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   576  
   577  	// when
   578  	_, err := eng.submitProposal(t, proposal)
   579  
   580  	// then
   581  	require.Error(t, err)
   582  	require.Equal(t, "market for automated purchase must be active", err.Error())
   583  }
   584  
   585  func testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketWithStateFails(t *testing.T) {
   586  	stoppedStates := []types.MarketState{types.MarketStateCancelled, types.MarketStateClosed, types.MarketStateRejected, types.MarketStateSettled, types.MarketStateTradingTerminated}
   587  	for _, state := range stoppedStates {
   588  		testSubmittingProposalForNewProtocolAutomatedPurchaseStoppedMarketFailes(t, state)
   589  	}
   590  }