code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_update_market_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  	"context"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/datasource"
    25  	dstypes "code.vegaprotocol.io/vega/core/datasource/common"
    26  	dsdefinition "code.vegaprotocol.io/vega/core/datasource/definition"
    27  	dserrors "code.vegaprotocol.io/vega/core/datasource/errors"
    28  	"code.vegaprotocol.io/vega/core/datasource/external/signedoracle"
    29  	"code.vegaprotocol.io/vega/core/events"
    30  	"code.vegaprotocol.io/vega/core/governance"
    31  	"code.vegaprotocol.io/vega/core/netparams"
    32  	"code.vegaprotocol.io/vega/core/types"
    33  	"code.vegaprotocol.io/vega/libs/num"
    34  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    35  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    36  
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  func TestProposalForMarketUpdate(t *testing.T) {
    42  	t.Run("Submitting a proposal for market update succeeds", testSubmittingProposalForMarketUpdateSucceeds)
    43  	t.Run("Submitting a proposal for market update with internal time termination succeeds", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationSucceeds)
    44  	t.Run("Submitting a proposal for market update with internal settling fails", testSubmittingProposalForMarketUpdateWithInternalTimeSetllingFails)
    45  	t.Run("Submitting a proposal for market update with internal time termination and 'less than' condition fails", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationWithLessThanConditionFails)
    46  	t.Run("Submitting a proposal for market update with termination in the past succeeds", testSubmittingProposalForMarketUpdateWithEarlyTerminationSucceeds)
    47  	t.Run("Submitting a proposal for market update with external termination using internal time key succeeds", testSubmittingProposalForMarketUpdateWithExternalSourceUsingInternalKeyTimeForTerminationSucceeds)
    48  	t.Run("Submitting a proposal for market update with empty settlement data fails", testSubmittingProposalForMarketUpdateWithEmptySettlementDataFails)
    49  	t.Run("Submitting a proposal for market update with empty termination data fails", testSubmittingProposalForMarketUpdateWithEmptyTerminationDataFails)
    50  	t.Run("Submitting a proposal for market update on unknown market fails", testSubmittingProposalForMarketUpdateForUnknownMarketFails)
    51  	t.Run("Submitting a proposal with internal time termination for market update on unknown market fails", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationForUnknownMarketFails)
    52  	t.Run("Submitting a proposal with internal time trigger termination fails", testSubmittingProposalForMarketUpdateWithInternalTimeTriggerTerminationFails)
    53  	t.Run("Submitting a proposal with internal time trigger settlement fails", testSubmittingProposalForMarketUpdateWithInternalTimeTriggerSettlementFails)
    54  
    55  	t.Run("Submitting a proposal for market update for not-enacted market fails", testSubmittingProposalForMarketUpdateForNotEnactedMarketFails)
    56  	t.Run("Submitting a proposal for market update with insufficient equity-like share fails", testSubmittingProposalForMarketUpdateWithInsufficientEquityLikeShareFails)
    57  	t.Run("Pre-enactment of market update proposal succeeds", testPreEnactmentOfMarketUpdateSucceeds)
    58  	t.Run("Pre-enactment of market with internal time termination update proposal succeeds", testPreEnactmentOfMarketUpdateWithInternalTimeTerminationSucceeds)
    59  
    60  	t.Run("Rejecting a proposal for market update succeeds", testRejectingProposalForMarketUpdateSucceeds)
    61  
    62  	t.Run("Voting without reaching minimum of tokens and equity-like shares makes the market update proposal declined", testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesMarketUpdateProposalPassed)
    63  	t.Run("Voting with a majority of 'yes' from tokens makes the market update proposal passed", testVotingWithMajorityOfYesFromTokenHoldersMakesMarketUpdateProposalPassed)
    64  	t.Run("Voting with a majority of 'no' from tokens makes the market update proposal declined", testVotingWithMajorityOfNoFromTokenHoldersMakesMarketUpdateProposalDeclined)
    65  	t.Run("Voting without reaching minimum of tokens and a majority of 'yes' from equity-like shares makes the market update proposal passed", testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesMarketUpdateProposalPassed)
    66  	t.Run("Voting without reaching minimum of tokens and a majority of 'no' from equity-like shares makes the market update proposal declined", testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesMarketUpdateProposalDeclined)
    67  	t.Run("Submitting a proposal with inconsistent products fails", TestSubmitProposalWithInconsistentProductFails)
    68  }
    69  
    70  func TestSubmittingProposalForMarketUpdateSucceeds(t *testing.T) {
    71  	eng := getTestEngine(t, time.Now())
    72  
    73  	ctx := context.Background()
    74  
    75  	// custom settings here:
    76  	eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredParticipationLP", "0")).Times(1)
    77  	require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredParticipationLP", "0"))
    78  
    79  	eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredParticipation", "0.07")).Times(1)
    80  	require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredParticipation", "0.07"))
    81  
    82  	eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredMajority", "0.66")).Times(1)
    83  	require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredMajority", "0.66"))
    84  
    85  	eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredMajorityLP", "0.66")).Times(1)
    86  	require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredMajorityLP", "0.66"))
    87  
    88  	// given
    89  	proposer := vgrand.RandomStr(5)
    90  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
    91  	marketID := proposal.MarketUpdate().MarketID
    92  
    93  	// setup
    94  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
    95  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.29)
    96  	eng.ensureExistingMarket(t, marketID)
    97  	eng.ensureGetMarketFuture(t, marketID)
    98  
    99  	// expect
   100  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   101  
   102  	// when
   103  	toSubmit, err := eng.submitProposal(t, proposal)
   104  
   105  	// then
   106  	require.NoError(t, err)
   107  	require.NotNil(t, toSubmit)
   108  
   109  	// setup
   110  	voterWithELS := vgrand.RandomStr(5)
   111  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
   112  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1)
   113  
   114  	// expect
   115  	eng.expectVoteEvent(t, voterWithELS, proposal.ID)
   116  
   117  	// when
   118  	err = eng.addYesVote(t, voterWithELS, proposal.ID)
   119  
   120  	// then
   121  	require.NoError(t, err)
   122  
   123  	// Closing the proposal.
   124  	// given
   125  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   126  
   127  	// setup
   128  	eng.ensureStakingAssetTotalSupply(t, 1000000)
   129  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
   130  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.29)
   131  
   132  	// // expect
   133  	eng.expectPassedProposalEvent(t, proposal.ID)
   134  	eng.expectVoteEvents(t)
   135  	eng.expectGetMarketState(t, proposal.ID)
   136  
   137  	// // when
   138  	eng.OnTick(context.Background(), afterClosing)
   139  }
   140  
   141  func testSubmittingProposalForMarketUpdateSucceeds(t *testing.T) {
   142  	eng := getTestEngine(t, time.Now())
   143  
   144  	// given
   145  	proposer := vgrand.RandomStr(5)
   146  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
   147  	marketID := proposal.MarketUpdate().MarketID
   148  
   149  	// setup
   150  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   151  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   152  	eng.ensureExistingMarket(t, marketID)
   153  	eng.ensureGetMarketFuture(t, marketID)
   154  
   155  	// expect
   156  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   157  
   158  	// when
   159  	toSubmit, err := eng.submitProposal(t, proposal)
   160  
   161  	// then
   162  	require.NoError(t, err)
   163  	require.NotNil(t, toSubmit)
   164  }
   165  
   166  func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationSucceeds(t *testing.T) {
   167  	eng := getTestEngine(t, time.Now())
   168  
   169  	// given
   170  	proposer := vgrand.RandomStr(5)
   171  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false)
   172  	marketID := proposal.MarketUpdate().MarketID
   173  
   174  	// setup
   175  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   176  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   177  	eng.ensureExistingMarket(t, marketID)
   178  	eng.ensureGetMarketFuture(t, marketID)
   179  
   180  	// expect
   181  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   182  
   183  	// when
   184  	toSubmit, err := eng.submitProposal(t, proposal)
   185  
   186  	// then
   187  	require.NoError(t, err)
   188  	require.NotNil(t, toSubmit)
   189  }
   190  
   191  func testSubmittingProposalForMarketUpdateWithInternalTimeSetllingFails(t *testing.T) {
   192  	eng := getTestEngine(t, time.Now())
   193  
   194  	// given
   195  	proposer := vgrand.RandomStr(5)
   196  
   197  	id := eng.newProposalID()
   198  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   199  	tm := now.Add(time.Hour * 24 * 365)
   200  	_, termBinding := produceTimeTriggeredDataSourceSpec(tm)
   201  
   202  	termination := datasource.NewDefinition(
   203  		datasource.ContentTypeInternalTimeTermination,
   204  	).SetTimeTriggerConditionConfig(
   205  		[]*dstypes.SpecCondition{
   206  			{
   207  				Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
   208  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
   209  			},
   210  		},
   211  	)
   212  
   213  	proposal := types.Proposal{
   214  		ID:        "market-1",
   215  		Reference: "ref-" + id,
   216  		Party:     proposer,
   217  		State:     types.ProposalStateOpen,
   218  		Terms: &types.ProposalTerms{
   219  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
   220  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
   221  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
   222  			Change: &types.ProposalTermsUpdateMarket{
   223  				UpdateMarket: &types.UpdateMarket{
   224  					MarketID: vgrand.RandomStr(5),
   225  					Changes: &types.UpdateMarketConfiguration{
   226  						Instrument: &types.UpdateInstrumentConfiguration{
   227  							Code: "CRYPTO:GBPVUSD/JUN20",
   228  							Name: "CRYPTO:GBPVUSD/JUN20",
   229  							Product: &types.UpdateInstrumentConfigurationFuture{
   230  								Future: &types.UpdateFutureProduct{
   231  									QuoteName: "VUSD",
   232  									DataSourceSpecForSettlementData: *datasource.NewDefinition(
   233  										datasource.ContentTypeOracle,
   234  									).SetTimeTriggerConditionConfig(
   235  										[]*dstypes.SpecCondition{
   236  											{
   237  												Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
   238  												Value:    fmt.Sprintf("%d", tm.UnixNano()),
   239  											},
   240  										},
   241  									),
   242  									DataSourceSpecForTradingTermination: *termination,
   243  									DataSourceSpecBinding:               termBinding,
   244  								},
   245  							},
   246  						},
   247  						RiskParameters: &types.UpdateMarketConfigurationLogNormal{
   248  							LogNormal: &types.LogNormalRiskModel{
   249  								RiskAversionParameter: num.DecimalFromFloat(0.01),
   250  								Tau:                   num.DecimalFromFloat(0.00011407711613050422),
   251  								Params: &types.LogNormalModelParams{
   252  									Mu:    num.DecimalZero(),
   253  									R:     num.DecimalFromFloat(0.016),
   254  									Sigma: num.DecimalFromFloat(0.09),
   255  								},
   256  							},
   257  						},
   258  						Metadata: []string{"asset_class:fx/crypto", "product:futures"},
   259  						LiquiditySLAParameters: &types.LiquiditySLAParams{
   260  							PriceRange:                  num.DecimalFromFloat(0.95),
   261  							CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
   262  							PerformanceHysteresisEpochs: 4,
   263  							SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
   264  						},
   265  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   266  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   267  						LiquidationStrategy: &types.LiquidationStrategy{
   268  							DisposalTimeStep:    10 * time.Second,
   269  							DisposalFraction:    num.DecimalFromFloat(0.1),
   270  							FullDisposalSize:    20,
   271  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   272  						},
   273  						TickSize: num.UintOne(),
   274  					},
   275  				},
   276  			},
   277  		},
   278  		Rationale: &types.ProposalRationale{
   279  			Description: "some description",
   280  		},
   281  	}
   282  
   283  	marketID := proposal.MarketUpdate().MarketID
   284  	// setup
   285  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   286  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   287  	eng.ensureExistingMarket(t, marketID)
   288  	eng.ensureGetMarketFuture(t, marketID)
   289  
   290  	// expect
   291  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
   292  
   293  	// when
   294  	toSubmit, err := eng.submitProposal(t, proposal)
   295  
   296  	// then
   297  	assert.Error(t, err, governance.ErrSettlementWithInternalDataSourceIsNotAllowed)
   298  	require.Nil(t, toSubmit)
   299  }
   300  
   301  func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationWithLessThanConditionFails(t *testing.T) {
   302  	eng := getTestEngine(t, time.Now())
   303  
   304  	// given
   305  	proposer := vgrand.RandomStr(5)
   306  
   307  	id := eng.newProposalID()
   308  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   309  	tm := now.Add(time.Hour * 24 * 365)
   310  
   311  	_, termBinding := produceTimeTriggeredDataSourceSpec(tm)
   312  
   313  	settl := datasource.NewDefinition(
   314  		datasource.ContentTypeOracle,
   315  	).SetOracleConfig(
   316  		&signedoracle.SpecConfiguration{
   317  			Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
   318  			Filters: []*dstypes.SpecFilter{
   319  				{
   320  					Key: &dstypes.SpecPropertyKey{
   321  						Name: "prices.ETH.value",
   322  						Type: datapb.PropertyKey_TYPE_INTEGER,
   323  					},
   324  					Conditions: []*dstypes.SpecCondition{},
   325  				},
   326  			},
   327  		},
   328  	)
   329  
   330  	term := datasource.NewDefinition(
   331  		datasource.ContentTypeInternalTimeTermination,
   332  	).SetTimeTriggerConditionConfig(
   333  		[]*dstypes.SpecCondition{
   334  			{
   335  				Operator: datapb.Condition_OPERATOR_LESS_THAN,
   336  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
   337  			},
   338  		},
   339  	)
   340  
   341  	riskParameters := types.UpdateMarketConfigurationLogNormal{
   342  		LogNormal: &types.LogNormalRiskModel{
   343  			RiskAversionParameter: num.DecimalFromFloat(0.01),
   344  			Tau:                   num.DecimalFromFloat(0.00011407711613050422),
   345  			Params: &types.LogNormalModelParams{
   346  				Mu:    num.DecimalZero(),
   347  				R:     num.DecimalFromFloat(0.016),
   348  				Sigma: num.DecimalFromFloat(0.09),
   349  			},
   350  		},
   351  	}
   352  
   353  	proposal := types.Proposal{
   354  		ID:        "market-1",
   355  		Reference: "ref-" + id,
   356  		Party:     proposer,
   357  		State:     types.ProposalStateOpen,
   358  		Terms: &types.ProposalTerms{
   359  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
   360  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
   361  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
   362  			Change: &types.ProposalTermsUpdateMarket{
   363  				UpdateMarket: &types.UpdateMarket{
   364  					MarketID: vgrand.RandomStr(5),
   365  					Changes: &types.UpdateMarketConfiguration{
   366  						Instrument: &types.UpdateInstrumentConfiguration{
   367  							Code: "CRYPTO:GBPVUSD/JUN20",
   368  							Name: "CRYPTO:GBPVUSD/JUN20",
   369  							Product: &types.UpdateInstrumentConfigurationFuture{
   370  								Future: &types.UpdateFutureProduct{
   371  									QuoteName:                           "VUSD",
   372  									DataSourceSpecForSettlementData:     *settl,
   373  									DataSourceSpecForTradingTermination: *term,
   374  									DataSourceSpecBinding:               termBinding,
   375  								},
   376  							},
   377  						},
   378  						RiskParameters: &riskParameters,
   379  						Metadata:       []string{"asset_class:fx/crypto", "product:futures"},
   380  						LiquiditySLAParameters: &types.LiquiditySLAParams{
   381  							PriceRange:                  num.DecimalFromFloat(0.95),
   382  							CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
   383  							PerformanceHysteresisEpochs: 4,
   384  							SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
   385  						},
   386  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   387  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   388  						LiquidationStrategy: &types.LiquidationStrategy{
   389  							DisposalTimeStep:    10 * time.Second,
   390  							DisposalFraction:    num.DecimalFromFloat(0.1),
   391  							FullDisposalSize:    20,
   392  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   393  						},
   394  						TickSize: num.UintOne(),
   395  					},
   396  				},
   397  			},
   398  		},
   399  		Rationale: &types.ProposalRationale{
   400  			Description: "some description",
   401  		},
   402  	}
   403  
   404  	marketID := proposal.MarketUpdate().MarketID
   405  
   406  	// setup
   407  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   408  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   409  	eng.ensureExistingMarket(t, marketID)
   410  	eng.ensureGetMarketFuture(t, marketID)
   411  
   412  	// expect
   413  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
   414  
   415  	// when
   416  	toSubmit, err := eng.submitProposal(t, proposal)
   417  
   418  	// then
   419  	assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition)
   420  	require.Nil(t, toSubmit)
   421  
   422  	term = datasource.NewDefinition(
   423  		datasource.ContentTypeOracle,
   424  	).SetTimeTriggerConditionConfig(
   425  		[]*dstypes.SpecCondition{
   426  			{
   427  				Operator: datapb.Condition_OPERATOR_LESS_THAN_OR_EQUAL,
   428  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
   429  			},
   430  		},
   431  	)
   432  
   433  	proposal = types.Proposal{
   434  		ID:        "market-1",
   435  		Reference: "ref-" + id,
   436  		Party:     proposer,
   437  		State:     types.ProposalStateOpen,
   438  		Terms: &types.ProposalTerms{
   439  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
   440  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
   441  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
   442  			Change: &types.ProposalTermsUpdateMarket{
   443  				UpdateMarket: &types.UpdateMarket{
   444  					MarketID: vgrand.RandomStr(5),
   445  					Changes: &types.UpdateMarketConfiguration{
   446  						Instrument: &types.UpdateInstrumentConfiguration{
   447  							Code: "CRYPTO:GBPVUSD/JUN20",
   448  							Name: "CRYPTO:GBPVUSD/JUN20",
   449  							Product: &types.UpdateInstrumentConfigurationFuture{
   450  								Future: &types.UpdateFutureProduct{
   451  									QuoteName:                           "VUSD",
   452  									DataSourceSpecForSettlementData:     *settl,
   453  									DataSourceSpecForTradingTermination: *term,
   454  									DataSourceSpecBinding:               termBinding,
   455  								},
   456  							},
   457  						},
   458  						RiskParameters: &riskParameters,
   459  						Metadata:       []string{"asset_class:fx/crypto", "product:futures"},
   460  						LiquiditySLAParameters: &types.LiquiditySLAParams{
   461  							PriceRange:                  num.DecimalFromFloat(0.95),
   462  							CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
   463  							PerformanceHysteresisEpochs: 4,
   464  							SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
   465  						},
   466  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   467  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   468  						LiquidationStrategy: &types.LiquidationStrategy{
   469  							DisposalTimeStep:    10 * time.Second,
   470  							DisposalFraction:    num.DecimalFromFloat(0.1),
   471  							FullDisposalSize:    20,
   472  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   473  						},
   474  						TickSize: num.UintOne(),
   475  					},
   476  				},
   477  			},
   478  		},
   479  		Rationale: &types.ProposalRationale{
   480  			Description: "some description",
   481  		},
   482  	}
   483  
   484  	marketID = proposal.MarketUpdate().MarketID
   485  
   486  	// setup
   487  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   488  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   489  	eng.ensureExistingMarket(t, marketID)
   490  	eng.ensureGetMarketFuture(t, marketID)
   491  
   492  	// expect
   493  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
   494  
   495  	// when
   496  	toSubmit, err = eng.submitProposal(t, proposal)
   497  
   498  	// then
   499  	assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition)
   500  
   501  	require.Nil(t, toSubmit)
   502  }
   503  
   504  func testSubmittingProposalForMarketUpdateWithExternalSourceUsingInternalKeyTimeForTerminationSucceeds(t *testing.T) {
   505  	eng := getTestEngine(t, time.Now())
   506  
   507  	// given
   508  	proposer := vgrand.RandomStr(5)
   509  	filter, binding := produceTimeTriggeredDataSourceSpec(time.Now())
   510  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), filter, binding, true)
   511  	marketID := proposal.MarketUpdate().MarketID
   512  
   513  	// setup
   514  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   515  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   516  	eng.ensureExistingMarket(t, marketID)
   517  	eng.ensureGetMarketFuture(t, marketID)
   518  
   519  	// expect
   520  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   521  
   522  	// when
   523  	toSubmit, err := eng.submitProposal(t, proposal)
   524  
   525  	// then
   526  	require.NoError(t, err)
   527  	require.NotNil(t, toSubmit)
   528  }
   529  
   530  func testSubmittingProposalForMarketUpdateWithEmptySettlementDataFails(t *testing.T) {
   531  	eng := getTestEngine(t, time.Now())
   532  
   533  	// given
   534  	proposer := vgrand.RandomStr(5)
   535  	id := eng.newProposalID()
   536  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   537  	tm := now.Add(time.Hour * 24 * 365)
   538  	_, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour))
   539  	term := datasource.NewDefinition(
   540  		datasource.ContentTypeInternalTimeTermination,
   541  	).SetTimeTriggerConditionConfig(
   542  		[]*dstypes.SpecCondition{
   543  			{
   544  				Operator: datapb.Condition_OPERATOR_LESS_THAN,
   545  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
   546  			},
   547  		},
   548  	)
   549  
   550  	riskParameters := types.UpdateMarketConfigurationLogNormal{
   551  		LogNormal: &types.LogNormalRiskModel{
   552  			RiskAversionParameter: num.DecimalFromFloat(0.01),
   553  			Tau:                   num.DecimalFromFloat(0.00011407711613050422),
   554  			Params: &types.LogNormalModelParams{
   555  				Mu:    num.DecimalZero(),
   556  				R:     num.DecimalFromFloat(0.016),
   557  				Sigma: num.DecimalFromFloat(0.09),
   558  			},
   559  		},
   560  	}
   561  	proposal := types.Proposal{
   562  		ID:        "market-1",
   563  		Reference: "ref-" + id,
   564  		Party:     proposer,
   565  		State:     types.ProposalStateOpen,
   566  		Terms: &types.ProposalTerms{
   567  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
   568  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
   569  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
   570  			Change: &types.ProposalTermsUpdateMarket{
   571  				UpdateMarket: &types.UpdateMarket{
   572  					MarketID: vgrand.RandomStr(5),
   573  					Changes: &types.UpdateMarketConfiguration{
   574  						Instrument: &types.UpdateInstrumentConfiguration{
   575  							Code: "CRYPTO:GBPVUSD/JUN20",
   576  							Name: "CRYPTO:GBPVUSD/JUN20",
   577  							Product: &types.UpdateInstrumentConfigurationFuture{
   578  								Future: &types.UpdateFutureProduct{
   579  									QuoteName:                           "VUSD",
   580  									DataSourceSpecForSettlementData:     dsdefinition.Definition{},
   581  									DataSourceSpecForTradingTermination: *term,
   582  									DataSourceSpecBinding:               binding,
   583  								},
   584  							},
   585  						},
   586  						RiskParameters: &riskParameters,
   587  						Metadata:       []string{"asset_class:fx/crypto", "product:futures"},
   588  						LiquiditySLAParameters: &types.LiquiditySLAParams{
   589  							PriceRange:                  num.DecimalFromFloat(0.95),
   590  							CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
   591  							PerformanceHysteresisEpochs: 4,
   592  							SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
   593  						},
   594  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   595  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   596  						LiquidationStrategy: &types.LiquidationStrategy{
   597  							DisposalTimeStep:    10 * time.Second,
   598  							DisposalFraction:    num.DecimalFromFloat(0.1),
   599  							FullDisposalSize:    20,
   600  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   601  						},
   602  						TickSize: num.UintOne(),
   603  					},
   604  				},
   605  			},
   606  		},
   607  		Rationale: &types.ProposalRationale{
   608  			Description: "some description",
   609  		},
   610  	}
   611  	marketID := proposal.MarketUpdate().MarketID
   612  
   613  	// setup
   614  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   615  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   616  	eng.ensureExistingMarket(t, marketID)
   617  	eng.ensureGetMarketFuture(t, marketID)
   618  
   619  	// expect
   620  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
   621  
   622  	// when
   623  	toSubmit, err := eng.submitProposal(t, proposal)
   624  
   625  	// then
   626  	assert.Error(t, err, governance.ErrMissingDataSourceSpecForSettlementData)
   627  	require.Nil(t, toSubmit)
   628  }
   629  
   630  func testSubmittingProposalForMarketUpdateWithEmptyTerminationDataFails(t *testing.T) {
   631  	eng := getTestEngine(t, time.Now())
   632  
   633  	// given
   634  	proposer := vgrand.RandomStr(5)
   635  	id := eng.newProposalID()
   636  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   637  	_, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour))
   638  	settl := datasource.NewDefinition(
   639  		datasource.ContentTypeOracle,
   640  	).SetOracleConfig(
   641  		&signedoracle.SpecConfiguration{
   642  			Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
   643  			Filters: []*dstypes.SpecFilter{
   644  				{
   645  					Key: &dstypes.SpecPropertyKey{
   646  						Name: "prices.ETH.value",
   647  						Type: datapb.PropertyKey_TYPE_INTEGER,
   648  					},
   649  					Conditions: []*dstypes.SpecCondition{},
   650  				},
   651  			},
   652  		},
   653  	)
   654  
   655  	riskParameters := types.UpdateMarketConfigurationLogNormal{
   656  		LogNormal: &types.LogNormalRiskModel{
   657  			RiskAversionParameter: num.DecimalFromFloat(0.01),
   658  			Tau:                   num.DecimalFromFloat(0.00011407711613050422),
   659  			Params: &types.LogNormalModelParams{
   660  				Mu:    num.DecimalZero(),
   661  				R:     num.DecimalFromFloat(0.016),
   662  				Sigma: num.DecimalFromFloat(0.09),
   663  			},
   664  		},
   665  	}
   666  	proposal := types.Proposal{
   667  		ID:        "market-1",
   668  		Reference: "ref-" + id,
   669  		Party:     proposer,
   670  		State:     types.ProposalStateOpen,
   671  		Terms: &types.ProposalTerms{
   672  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
   673  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
   674  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
   675  			Change: &types.ProposalTermsUpdateMarket{
   676  				UpdateMarket: &types.UpdateMarket{
   677  					MarketID: vgrand.RandomStr(5),
   678  					Changes: &types.UpdateMarketConfiguration{
   679  						Instrument: &types.UpdateInstrumentConfiguration{
   680  							Code: "CRYPTO:GBPVUSD/JUN20",
   681  							Name: "CRYPTO:GBPVUSD/JUN20",
   682  							Product: &types.UpdateInstrumentConfigurationFuture{
   683  								Future: &types.UpdateFutureProduct{
   684  									QuoteName:                           "VUSD",
   685  									DataSourceSpecForSettlementData:     *settl,
   686  									DataSourceSpecForTradingTermination: dsdefinition.Definition{},
   687  									DataSourceSpecBinding:               binding,
   688  								},
   689  							},
   690  						},
   691  						RiskParameters: &riskParameters,
   692  						Metadata:       []string{"asset_class:fx/crypto", "product:futures"},
   693  						LiquiditySLAParameters: &types.LiquiditySLAParams{
   694  							PriceRange:                  num.DecimalFromFloat(0.95),
   695  							CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
   696  							PerformanceHysteresisEpochs: 4,
   697  							SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
   698  						},
   699  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   700  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   701  						LiquidationStrategy: &types.LiquidationStrategy{
   702  							DisposalTimeStep:    10 * time.Second,
   703  							DisposalFraction:    num.DecimalFromFloat(0.1),
   704  							FullDisposalSize:    20,
   705  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   706  						},
   707  						TickSize: num.UintOne(),
   708  					},
   709  				},
   710  			},
   711  		},
   712  		Rationale: &types.ProposalRationale{
   713  			Description: "some description",
   714  		},
   715  	}
   716  	marketID := proposal.MarketUpdate().MarketID
   717  
   718  	// setup
   719  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   720  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   721  	eng.ensureExistingMarket(t, marketID)
   722  	eng.ensureGetMarketFuture(t, marketID)
   723  
   724  	// expect
   725  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
   726  
   727  	// when
   728  	toSubmit, err := eng.submitProposal(t, proposal)
   729  
   730  	// then
   731  	assert.Error(t, err, governance.ErrMissingDataSourceSpecForTradingTermination)
   732  	require.Nil(t, toSubmit)
   733  }
   734  
   735  func testSubmittingProposalForMarketUpdateWithEarlyTerminationSucceeds(t *testing.T) {
   736  	eng := getTestEngine(t, time.Now())
   737  
   738  	// Submit proposal.
   739  	// given
   740  	proposer := vgrand.RandomStr(5)
   741  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
   742  	marketID := proposal.MarketUpdate().MarketID
   743  
   744  	proposal.Terms.Change.(*types.ProposalTermsUpdateMarket).UpdateMarket.Changes.Instrument.Product.(*types.UpdateInstrumentConfigurationFuture).Future.DataSourceSpecForTradingTermination.UpdateFilters(
   745  		[]*dstypes.SpecFilter{
   746  			{
   747  				Key: &dstypes.SpecPropertyKey{
   748  					Name: "vegaprotocol.builtin.timestamp",
   749  					Type: datapb.PropertyKey_TYPE_TIMESTAMP,
   750  				},
   751  				Conditions: []*dstypes.SpecCondition{
   752  					{
   753  						Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
   754  						Value:    "0", // change to internal timestamp that is in the past
   755  					},
   756  				},
   757  			},
   758  		},
   759  	)
   760  	proposal.Terms.Change.(*types.ProposalTermsUpdateMarket).UpdateMarket.Changes.Instrument.Product.(*types.UpdateInstrumentConfigurationFuture).Future.DataSourceSpecBinding.TradingTerminationProperty = "vegaprotocol.builtin.timestamp"
   761  
   762  	// setup
   763  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   764  	eng.ensureExistingMarket(t, marketID)
   765  	eng.ensureGetMarketFuture(t, marketID)
   766  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   767  	eng.ensureAllAssetEnabled(t)
   768  
   769  	// expect
   770  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   771  
   772  	// when
   773  	_, err := eng.submitProposal(t, proposal)
   774  
   775  	// then
   776  	require.NoError(t, err)
   777  
   778  	// Vote 'YES' with 10 tokens.
   779  	// given
   780  	voterWithToken1 := vgrand.RandomStr(5)
   781  
   782  	// setup
   783  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   784  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
   785  
   786  	// expect
   787  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
   788  
   789  	// when
   790  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
   791  
   792  	// then
   793  	require.NoError(t, err)
   794  
   795  	// Vote 'NO' with 2 tokens.
   796  	// given
   797  	voterWithToken2 := vgrand.RandomStr(5)
   798  
   799  	// Close the proposal.
   800  	// given
   801  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   802  
   803  	// setup
   804  	eng.ensureStakingAssetTotalSupply(t, 13)
   805  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   806  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   807  
   808  	// expect
   809  	eng.expectPassedProposalEvent(t, proposal.ID)
   810  	eng.expectVoteEvents(t)
   811  	eng.expectGetMarketState(t, marketID)
   812  
   813  	// when
   814  	eng.OnTick(context.Background(), afterClosing)
   815  
   816  	// Enact the proposal.
   817  	// given
   818  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
   819  	existingMarket := types.Market{
   820  		ID: marketID,
   821  		TradableInstrument: &types.TradableInstrument{
   822  			Instrument: &types.Instrument{
   823  				Name: vgrand.RandomStr(10),
   824  				Product: &types.InstrumentFuture{
   825  					Future: &types.Future{
   826  						SettlementAsset: "BTC",
   827  					},
   828  				},
   829  			},
   830  		},
   831  		DecimalPlaces:         3,
   832  		PositionDecimalPlaces: 4,
   833  		OpeningAuction: &types.AuctionDuration{
   834  			Duration: 42,
   835  		},
   836  	}
   837  
   838  	// setup
   839  	eng.ensureGetMarket(t, marketID, existingMarket)
   840  
   841  	// when
   842  	enacted, _ := eng.OnTick(context.Background(), afterEnactment)
   843  
   844  	// then
   845  	require.NotEmpty(t, enacted)
   846  	require.True(t, enacted[0].IsUpdateMarket())
   847  	updatedMarket := enacted[0].UpdateMarket()
   848  	assert.Equal(t, existingMarket.ID, updatedMarket.ID)
   849  	assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name)
   850  
   851  	assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset)
   852  	assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces)
   853  	assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces)
   854  	assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration)
   855  }
   856  
   857  func testSubmittingProposalForMarketUpdateForUnknownMarketFails(t *testing.T) {
   858  	eng := getTestEngine(t, time.Now())
   859  
   860  	// given
   861  	proposer := vgrand.RandomStr(5)
   862  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
   863  	marketID := proposal.MarketUpdate().MarketID
   864  
   865  	// setup
   866  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
   867  	eng.ensureNonExistingMarket(t, marketID)
   868  
   869  	// expect
   870  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   871  
   872  	// when
   873  	toSubmit, err := eng.submitProposal(t, proposal)
   874  
   875  	// then
   876  	require.ErrorIs(t, governance.ErrMarketDoesNotExist, err)
   877  	require.Nil(t, toSubmit)
   878  }
   879  
   880  func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationForUnknownMarketFails(t *testing.T) {
   881  	eng := getTestEngine(t, time.Now())
   882  
   883  	// given
   884  	proposer := vgrand.RandomStr(5)
   885  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false)
   886  	marketID := proposal.MarketUpdate().MarketID
   887  
   888  	// setup
   889  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
   890  	eng.ensureNonExistingMarket(t, marketID)
   891  
   892  	// expect
   893  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
   894  
   895  	// when
   896  	toSubmit, err := eng.submitProposal(t, proposal)
   897  
   898  	// then
   899  	require.ErrorIs(t, governance.ErrMarketDoesNotExist, err)
   900  	require.Nil(t, toSubmit)
   901  }
   902  
   903  func testSubmittingProposalForMarketUpdateWithInternalTimeTriggerTerminationFails(t *testing.T) {
   904  	eng := getTestEngine(t, time.Now())
   905  
   906  	// given
   907  	proposer := vgrand.RandomStr(5)
   908  	id := eng.newProposalID()
   909  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   910  	tm := now.Add(time.Hour * 24 * 365)
   911  	_, binding := produceTimeTriggeredDataSourceSpec(tm)
   912  	settl := datasource.NewDefinition(
   913  		datasource.ContentTypeOracle,
   914  	).SetOracleConfig(
   915  		&signedoracle.SpecConfiguration{
   916  			Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
   917  			Filters: []*dstypes.SpecFilter{
   918  				{
   919  					Key: &dstypes.SpecPropertyKey{
   920  						Name: "prices.ETH.value",
   921  						Type: datapb.PropertyKey_TYPE_INTEGER,
   922  					},
   923  					Conditions: []*dstypes.SpecCondition{},
   924  				},
   925  			},
   926  		},
   927  	)
   928  
   929  	term := datasource.NewDefinition(
   930  		datasource.ContentTypeInternalTimeTriggerTermination,
   931  	).SetTimeTriggerConditionConfig(
   932  		[]*dstypes.SpecCondition{
   933  			{
   934  				Operator: datapb.Condition_OPERATOR_GREATER_THAN,
   935  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
   936  			},
   937  		})
   938  
   939  	riskParameters := types.UpdateMarketConfigurationLogNormal{
   940  		LogNormal: &types.LogNormalRiskModel{
   941  			RiskAversionParameter: num.DecimalFromFloat(0.01),
   942  			Tau:                   num.DecimalFromFloat(0.00011407711613050422),
   943  			Params: &types.LogNormalModelParams{
   944  				Mu:    num.DecimalZero(),
   945  				R:     num.DecimalFromFloat(0.016),
   946  				Sigma: num.DecimalFromFloat(0.09),
   947  			},
   948  		},
   949  	}
   950  	proposal := types.Proposal{
   951  		ID:        "market-1",
   952  		Reference: "ref-" + id,
   953  		Party:     proposer,
   954  		State:     types.ProposalStateOpen,
   955  		Terms: &types.ProposalTerms{
   956  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
   957  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
   958  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
   959  			Change: &types.ProposalTermsUpdateMarket{
   960  				UpdateMarket: &types.UpdateMarket{
   961  					MarketID: vgrand.RandomStr(5),
   962  					Changes: &types.UpdateMarketConfiguration{
   963  						Instrument: &types.UpdateInstrumentConfiguration{
   964  							Code: "CRYPTO:GBPVUSD/JUN20",
   965  							Name: "CRYPTO:GBPVUSD/JUN20",
   966  							Product: &types.UpdateInstrumentConfigurationFuture{
   967  								Future: &types.UpdateFutureProduct{
   968  									QuoteName:                           "VUSD",
   969  									DataSourceSpecForSettlementData:     *settl,
   970  									DataSourceSpecForTradingTermination: *term,
   971  									DataSourceSpecBinding:               binding,
   972  								},
   973  							},
   974  						},
   975  						RiskParameters:          &riskParameters,
   976  						Metadata:                []string{"asset_class:fx/crypto", "product:futures"},
   977  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
   978  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
   979  						LiquidationStrategy: &types.LiquidationStrategy{
   980  							DisposalTimeStep:    10 * time.Second,
   981  							DisposalFraction:    num.DecimalFromFloat(0.1),
   982  							FullDisposalSize:    20,
   983  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
   984  						},
   985  						TickSize: num.UintOne(),
   986  					},
   987  				},
   988  			},
   989  		},
   990  		Rationale: &types.ProposalRationale{
   991  			Description: "some description",
   992  		},
   993  	}
   994  	marketID := proposal.MarketUpdate().MarketID
   995  
   996  	// setup
   997  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   998  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   999  	eng.ensureExistingMarket(t, marketID)
  1000  	eng.ensureGetMarketFuture(t, marketID)
  1001  
  1002  	// expect
  1003  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
  1004  
  1005  	// when
  1006  	toSubmit, err := eng.submitProposal(t, proposal)
  1007  
  1008  	// then
  1009  	assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed)
  1010  	require.Nil(t, toSubmit)
  1011  }
  1012  
  1013  func testSubmittingProposalForMarketUpdateWithInternalTimeTriggerSettlementFails(t *testing.T) {
  1014  	eng := getTestEngine(t, time.Now())
  1015  
  1016  	// given
  1017  	proposer := vgrand.RandomStr(5)
  1018  	id := eng.newProposalID()
  1019  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
  1020  	tm := now.Add(time.Hour * 24 * 365)
  1021  	_, binding := produceTimeTriggeredDataSourceSpec(tm)
  1022  	settl := datasource.NewDefinition(
  1023  		datasource.ContentTypeInternalTimeTriggerTermination,
  1024  	).SetTimeTriggerConditionConfig(
  1025  		[]*dstypes.SpecCondition{
  1026  			{
  1027  				Operator: datapb.Condition_OPERATOR_GREATER_THAN,
  1028  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
  1029  			},
  1030  		})
  1031  
  1032  	term := datasource.NewDefinition(
  1033  		datasource.ContentTypeInternalTimeTermination,
  1034  	).SetTimeTriggerConditionConfig(
  1035  		[]*dstypes.SpecCondition{
  1036  			{
  1037  				Operator: datapb.Condition_OPERATOR_GREATER_THAN,
  1038  				Value:    fmt.Sprintf("%d", tm.UnixNano()),
  1039  			},
  1040  		},
  1041  	)
  1042  	riskParameters := types.UpdateMarketConfigurationLogNormal{
  1043  		LogNormal: &types.LogNormalRiskModel{
  1044  			RiskAversionParameter: num.DecimalFromFloat(0.01),
  1045  			Tau:                   num.DecimalFromFloat(0.00011407711613050422),
  1046  			Params: &types.LogNormalModelParams{
  1047  				Mu:    num.DecimalZero(),
  1048  				R:     num.DecimalFromFloat(0.016),
  1049  				Sigma: num.DecimalFromFloat(0.09),
  1050  			},
  1051  		},
  1052  	}
  1053  	proposal := types.Proposal{
  1054  		ID:        "market-1",
  1055  		Reference: "ref-" + id,
  1056  		Party:     proposer,
  1057  		State:     types.ProposalStateOpen,
  1058  		Terms: &types.ProposalTerms{
  1059  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  1060  			EnactmentTimestamp:  now.Add(1 * 48 * time.Hour).Unix(),
  1061  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  1062  			Change: &types.ProposalTermsUpdateMarket{
  1063  				UpdateMarket: &types.UpdateMarket{
  1064  					MarketID: vgrand.RandomStr(5),
  1065  					Changes: &types.UpdateMarketConfiguration{
  1066  						Instrument: &types.UpdateInstrumentConfiguration{
  1067  							Code: "CRYPTO:GBPVUSD/JUN20",
  1068  							Name: "CRYPTO:GBPVUSD/JUN20",
  1069  							Product: &types.UpdateInstrumentConfigurationFuture{
  1070  								Future: &types.UpdateFutureProduct{
  1071  									QuoteName:                           "VUSD",
  1072  									DataSourceSpecForSettlementData:     *settl,
  1073  									DataSourceSpecForTradingTermination: *term,
  1074  									DataSourceSpecBinding:               binding,
  1075  								},
  1076  							},
  1077  						},
  1078  						RiskParameters:          &riskParameters,
  1079  						Metadata:                []string{"asset_class:fx/crypto", "product:futures"},
  1080  						LinearSlippageFactor:    num.DecimalFromFloat(0.1),
  1081  						QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
  1082  						LiquidationStrategy: &types.LiquidationStrategy{
  1083  							DisposalTimeStep:    10 * time.Second,
  1084  							DisposalFraction:    num.DecimalFromFloat(0.1),
  1085  							FullDisposalSize:    20,
  1086  							MaxFractionConsumed: num.DecimalFromFloat(0.01),
  1087  						},
  1088  						TickSize: num.UintOne(),
  1089  					},
  1090  				},
  1091  			},
  1092  		},
  1093  		Rationale: &types.ProposalRationale{
  1094  			Description: "some description",
  1095  		},
  1096  	}
  1097  	marketID := proposal.MarketUpdate().MarketID
  1098  
  1099  	// setup
  1100  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
  1101  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
  1102  	eng.ensureExistingMarket(t, marketID)
  1103  	eng.ensureGetMarketFuture(t, marketID)
  1104  
  1105  	// expect
  1106  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
  1107  
  1108  	// when
  1109  	toSubmit, err := eng.submitProposal(t, proposal)
  1110  
  1111  	// then
  1112  	assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed)
  1113  	require.Nil(t, toSubmit)
  1114  }
  1115  
  1116  func testSubmittingProposalForMarketUpdateForNotEnactedMarketFails(t *testing.T) {
  1117  	eng := getTestEngine(t, time.Now())
  1118  
  1119  	// given
  1120  	proposer := vgrand.RandomStr(5)
  1121  	newMarketProposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
  1122  	marketID := newMarketProposal.ID
  1123  
  1124  	// setup
  1125  	eng.ensureAllAssetEnabled(t)
  1126  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
  1127  	eng.expectOpenProposalEvent(t, proposer, marketID)
  1128  
  1129  	// when
  1130  	toSubmit, err := eng.submitProposal(t, newMarketProposal)
  1131  
  1132  	// then
  1133  	require.NoError(t, err)
  1134  	require.NotNil(t, toSubmit)
  1135  	assert.True(t, toSubmit.IsNewMarket())
  1136  
  1137  	// given
  1138  	updateMarketProposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1139  	updateMarketProposal.MarketUpdate().MarketID = marketID
  1140  
  1141  	// setup
  1142  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
  1143  	eng.ensureExistingMarket(t, marketID)
  1144  
  1145  	// expect
  1146  	eng.expectRejectedProposalEvent(t, proposer, updateMarketProposal.ID, types.ProposalErrorInvalidMarket)
  1147  
  1148  	// when
  1149  	toSubmit, err = eng.submitProposal(t, updateMarketProposal)
  1150  
  1151  	// then
  1152  	require.ErrorIs(t, governance.ErrMarketProposalStillOpen, err)
  1153  	require.Nil(t, toSubmit)
  1154  
  1155  	// now the original market proposal passes
  1156  	// given
  1157  	voter1 := vgrand.RandomStr(5)
  1158  	eng.ensureTokenBalanceForParty(t, voter1, 7)
  1159  	eng.expectVoteEvent(t, voter1, marketID)
  1160  	err = eng.addYesVote(t, voter1, marketID)
  1161  	require.NoError(t, err)
  1162  
  1163  	afterClosing := time.Unix(newMarketProposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1164  	eng.ensureStakingAssetTotalSupply(t, 10)
  1165  	eng.ensureTokenBalanceForParty(t, voter1, 7)
  1166  	eng.expectPassedProposalEvent(t, marketID)
  1167  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7")
  1168  	eng.expectGetMarketState(t, marketID)
  1169  	eng.OnTick(context.Background(), afterClosing)
  1170  
  1171  	// submitting now the market proposal has passed should work
  1172  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
  1173  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
  1174  	eng.ensureExistingMarket(t, marketID)
  1175  	eng.ensureGetMarketFuture(t, marketID)
  1176  	eng.expectOpenProposalEvent(t, proposer, updateMarketProposal.ID)
  1177  	toSubmit, err = eng.submitProposal(t, updateMarketProposal)
  1178  	require.NoError(t, err)
  1179  	require.NotNil(t, toSubmit)
  1180  }
  1181  
  1182  func testSubmittingProposalForMarketUpdateWithInsufficientEquityLikeShareFails(t *testing.T) {
  1183  	eng := getTestEngine(t, time.Now())
  1184  
  1185  	// given
  1186  	party := vgrand.RandomStr(5)
  1187  	proposal := eng.newProposalForMarketUpdate("״market-1", party, eng.tsvc.GetTimeNow(), nil, nil, true)
  1188  	marketID := proposal.MarketUpdate().MarketID
  1189  
  1190  	// setup
  1191  	// eng.ensureTokenBalanceForParty(t, party, 100)
  1192  	eng.ensureExistingMarket(t, marketID)
  1193  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.05)
  1194  
  1195  	// expect
  1196  	eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientTokens)
  1197  
  1198  	// when
  1199  	toSubmit, err := eng.submitProposal(t, proposal)
  1200  
  1201  	// then
  1202  	require.Error(t, err)
  1203  	assert.Contains(t, err.Error(), "no balance for party")
  1204  	require.Nil(t, toSubmit)
  1205  }
  1206  
  1207  func testPreEnactmentOfMarketUpdateSucceeds(t *testing.T) {
  1208  	eng := getTestEngine(t, time.Now())
  1209  
  1210  	// Submit proposal.
  1211  	// given
  1212  	proposer := vgrand.RandomStr(5)
  1213  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1214  	marketID := proposal.MarketUpdate().MarketID
  1215  
  1216  	// setup
  1217  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1218  	eng.ensureExistingMarket(t, marketID)
  1219  	eng.ensureGetMarketFuture(t, marketID)
  1220  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1221  	eng.ensureAllAssetEnabled(t)
  1222  
  1223  	// expect
  1224  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1225  
  1226  	// when
  1227  	_, err := eng.submitProposal(t, proposal)
  1228  
  1229  	// then
  1230  	require.NoError(t, err)
  1231  
  1232  	// Vote 'YES' with 10 tokens.
  1233  	// given
  1234  	voterWithToken1 := vgrand.RandomStr(5)
  1235  
  1236  	// setup
  1237  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1238  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
  1239  
  1240  	// expect
  1241  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
  1242  
  1243  	// when
  1244  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
  1245  
  1246  	// then
  1247  	require.NoError(t, err)
  1248  
  1249  	// Vote 'NO' with 2 tokens.
  1250  	// given
  1251  	voterWithToken2 := vgrand.RandomStr(5)
  1252  
  1253  	// setup
  1254  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1255  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
  1256  
  1257  	// expect
  1258  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
  1259  
  1260  	// then
  1261  	err = eng.addNoVote(t, voterWithToken2, proposal.ID)
  1262  
  1263  	// then
  1264  	require.NoError(t, err)
  1265  
  1266  	// Close the proposal.
  1267  	// given
  1268  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1269  
  1270  	// setup
  1271  	eng.ensureStakingAssetTotalSupply(t, 13)
  1272  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1273  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1274  
  1275  	// expect
  1276  	eng.expectPassedProposalEvent(t, proposal.ID)
  1277  	eng.expectVoteEvents(t)
  1278  	eng.expectGetMarketState(t, marketID)
  1279  
  1280  	// when
  1281  	eng.OnTick(context.Background(), afterClosing)
  1282  
  1283  	// Enact the proposal.
  1284  	// given
  1285  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
  1286  	existingMarket := types.Market{
  1287  		ID: marketID,
  1288  		TradableInstrument: &types.TradableInstrument{
  1289  			Instrument: &types.Instrument{
  1290  				Name: vgrand.RandomStr(10),
  1291  				Product: &types.InstrumentFuture{
  1292  					Future: &types.Future{
  1293  						SettlementAsset: "BTC",
  1294  					},
  1295  				},
  1296  			},
  1297  		},
  1298  		DecimalPlaces:         3,
  1299  		PositionDecimalPlaces: 4,
  1300  		OpeningAuction: &types.AuctionDuration{
  1301  			Duration: 42,
  1302  		},
  1303  	}
  1304  
  1305  	// setup
  1306  	eng.ensureGetMarket(t, marketID, existingMarket)
  1307  
  1308  	// when
  1309  	enacted, _ := eng.OnTick(context.Background(), afterEnactment)
  1310  
  1311  	// then
  1312  	require.NotEmpty(t, enacted)
  1313  	require.True(t, enacted[0].IsUpdateMarket())
  1314  	updatedMarket := enacted[0].UpdateMarket()
  1315  	assert.Equal(t, existingMarket.ID, updatedMarket.ID)
  1316  	assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name)
  1317  	assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset)
  1318  	assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces)
  1319  	assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces)
  1320  	assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration)
  1321  }
  1322  
  1323  func testPreEnactmentOfMarketUpdateWithInternalTimeTerminationSucceeds(t *testing.T) {
  1324  	eng := getTestEngine(t, time.Now())
  1325  
  1326  	// Submit proposal.
  1327  	// given
  1328  	proposer := vgrand.RandomStr(5)
  1329  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false)
  1330  	marketID := proposal.MarketUpdate().MarketID
  1331  
  1332  	// setup
  1333  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1334  	eng.ensureExistingMarket(t, marketID)
  1335  	eng.ensureGetMarketFuture(t, marketID)
  1336  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1337  	eng.ensureAllAssetEnabled(t)
  1338  
  1339  	// expect
  1340  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1341  
  1342  	// when
  1343  	_, err := eng.submitProposal(t, proposal)
  1344  
  1345  	// then
  1346  	require.NoError(t, err)
  1347  
  1348  	// Vote 'YES' with 10 tokens.
  1349  	// given
  1350  	voterWithToken1 := vgrand.RandomStr(5)
  1351  
  1352  	// setup
  1353  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1354  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
  1355  
  1356  	// expect
  1357  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
  1358  
  1359  	// when
  1360  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
  1361  
  1362  	// then
  1363  	require.NoError(t, err)
  1364  
  1365  	// Vote 'NO' with 2 tokens.
  1366  	// given
  1367  	voterWithToken2 := vgrand.RandomStr(5)
  1368  
  1369  	// setup
  1370  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1371  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
  1372  
  1373  	// expect
  1374  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
  1375  
  1376  	// then
  1377  	err = eng.addNoVote(t, voterWithToken2, proposal.ID)
  1378  
  1379  	// then
  1380  	require.NoError(t, err)
  1381  
  1382  	// Close the proposal.
  1383  	// given
  1384  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1385  
  1386  	// setup
  1387  	eng.ensureStakingAssetTotalSupply(t, 13)
  1388  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1389  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1390  
  1391  	// expect
  1392  	eng.expectPassedProposalEvent(t, proposal.ID)
  1393  	eng.expectVoteEvents(t)
  1394  	eng.expectGetMarketState(t, marketID)
  1395  
  1396  	// when
  1397  	eng.OnTick(context.Background(), afterClosing)
  1398  
  1399  	// Enact the proposal.
  1400  	// given
  1401  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
  1402  	existingMarket := types.Market{
  1403  		ID: marketID,
  1404  		TradableInstrument: &types.TradableInstrument{
  1405  			Instrument: &types.Instrument{
  1406  				Name: vgrand.RandomStr(10),
  1407  				Product: &types.InstrumentFuture{
  1408  					Future: &types.Future{
  1409  						SettlementAsset: "BTC",
  1410  					},
  1411  				},
  1412  			},
  1413  		},
  1414  		DecimalPlaces:         3,
  1415  		PositionDecimalPlaces: 4,
  1416  		OpeningAuction: &types.AuctionDuration{
  1417  			Duration: 42,
  1418  		},
  1419  	}
  1420  
  1421  	// setup
  1422  	eng.ensureGetMarket(t, marketID, existingMarket)
  1423  
  1424  	// when
  1425  	enacted, _ := eng.OnTick(context.Background(), afterEnactment)
  1426  
  1427  	// then
  1428  	require.NotEmpty(t, enacted)
  1429  	require.True(t, enacted[0].IsUpdateMarket())
  1430  	updatedMarket := enacted[0].UpdateMarket()
  1431  	assert.Equal(t, existingMarket.ID, updatedMarket.ID)
  1432  	assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name)
  1433  	assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset)
  1434  	assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces)
  1435  	assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces)
  1436  	assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration)
  1437  }
  1438  
  1439  func testRejectingProposalForMarketUpdateSucceeds(t *testing.T) {
  1440  	eng := getTestEngine(t, time.Now())
  1441  
  1442  	// given
  1443  	party := vgrand.RandomStr(5)
  1444  	proposal := eng.newProposalForMarketUpdate("market-1", party, eng.tsvc.GetTimeNow(), nil, nil, true)
  1445  	marketID := proposal.MarketUpdate().MarketID
  1446  
  1447  	// setup
  1448  	eng.ensureAllAssetEnabled(t)
  1449  	eng.ensureExistingMarket(t, marketID)
  1450  	eng.ensureGetMarketFuture(t, marketID)
  1451  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.7)
  1452  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1")
  1453  	eng.ensureTokenBalanceForParty(t, party, 10000)
  1454  
  1455  	// expect
  1456  	eng.expectOpenProposalEvent(t, party, proposal.ID)
  1457  
  1458  	// when
  1459  	toSubmit, err := eng.submitProposal(t, proposal)
  1460  
  1461  	// then
  1462  	require.NoError(t, err)
  1463  	require.NotNil(t, toSubmit)
  1464  
  1465  	// expect
  1466  	eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorCouldNotInstantiateMarket)
  1467  
  1468  	// when
  1469  	err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError)
  1470  
  1471  	// then
  1472  	require.NoError(t, err)
  1473  
  1474  	// when
  1475  	// Just one more time to make sure it was removed from proposals.
  1476  	err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError)
  1477  
  1478  	// then
  1479  	assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error())
  1480  }
  1481  
  1482  func testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesMarketUpdateProposalPassed(t *testing.T) {
  1483  	eng := getTestEngine(t, time.Now())
  1484  
  1485  	// Submit proposal.
  1486  	// given
  1487  	proposer := vgrand.RandomStr(5)
  1488  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1489  	marketID := proposal.MarketUpdate().MarketID
  1490  
  1491  	// setup
  1492  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
  1493  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipationLP, "0.5")
  1494  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
  1495  	eng.ensureExistingMarket(t, marketID)
  1496  	eng.ensureGetMarketFuture(t, marketID)
  1497  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1498  	eng.ensureAllAssetEnabled(t)
  1499  
  1500  	// expect
  1501  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1502  
  1503  	// when
  1504  	_, err := eng.submitProposal(t, proposal)
  1505  
  1506  	// then
  1507  	require.NoError(t, err)
  1508  
  1509  	// Vote using a token holder without equity-like share.
  1510  	// when
  1511  	voterWithToken := vgrand.RandomStr(5)
  1512  
  1513  	// setup
  1514  	eng.ensureTokenBalanceForParty(t, voterWithToken, 1)
  1515  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1516  
  1517  	// expect
  1518  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
  1519  
  1520  	// when
  1521  	err = eng.addYesVote(t, voterWithToken, proposal.ID)
  1522  
  1523  	// then
  1524  	require.NoError(t, err)
  1525  
  1526  	// Vote using equity-like share holder without tokens.
  1527  	// given
  1528  	voterWithELS := vgrand.RandomStr(5)
  1529  
  1530  	// setup
  1531  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
  1532  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1)
  1533  
  1534  	// expect
  1535  	eng.expectVoteEvent(t, voterWithELS, proposal.ID)
  1536  
  1537  	// when
  1538  	err = eng.addNoVote(t, voterWithELS, proposal.ID)
  1539  
  1540  	// then
  1541  	require.NoError(t, err)
  1542  
  1543  	// Closing the proposal.
  1544  	// given
  1545  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1546  
  1547  	// setup
  1548  	eng.ensureStakingAssetTotalSupply(t, 10)
  1549  	eng.ensureTokenBalanceForParty(t, voterWithToken, 1)
  1550  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1551  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
  1552  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1)
  1553  
  1554  	// expect
  1555  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached)
  1556  	eng.expectVoteEvents(t)
  1557  	eng.expectGetMarketState(t, proposal.ID)
  1558  
  1559  	// when
  1560  	eng.OnTick(context.Background(), afterClosing)
  1561  }
  1562  
  1563  func testVotingWithMajorityOfYesFromTokenHoldersMakesMarketUpdateProposalPassed(t *testing.T) {
  1564  	eng := getTestEngine(t, time.Now())
  1565  
  1566  	// Submit proposal.
  1567  	// given
  1568  	proposer := vgrand.RandomStr(5)
  1569  	proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1570  	marketID := proposal.MarketUpdate().MarketID
  1571  
  1572  	// setup
  1573  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1574  	eng.ensureExistingMarket(t, marketID)
  1575  	eng.ensureGetMarketFuture(t, marketID)
  1576  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1577  	eng.ensureAllAssetEnabled(t)
  1578  
  1579  	// expect
  1580  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1581  
  1582  	// when
  1583  	_, err := eng.submitProposal(t, proposal)
  1584  
  1585  	// then
  1586  	require.NoError(t, err)
  1587  
  1588  	// Vote 'YES' with 10 tokens.
  1589  	// given
  1590  	voterWithToken1 := vgrand.RandomStr(5)
  1591  
  1592  	// setup
  1593  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1594  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
  1595  
  1596  	// expect
  1597  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
  1598  
  1599  	// when
  1600  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
  1601  
  1602  	// then
  1603  	require.NoError(t, err)
  1604  
  1605  	// Vote 'NO' with 2 tokens.
  1606  	// given
  1607  	voterWithToken2 := vgrand.RandomStr(5)
  1608  
  1609  	// setup
  1610  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1611  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
  1612  
  1613  	// expect
  1614  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
  1615  
  1616  	// then
  1617  	err = eng.addNoVote(t, voterWithToken2, proposal.ID)
  1618  
  1619  	// then
  1620  	require.NoError(t, err)
  1621  
  1622  	// Vote 'NO' with 0.1 of equity-like share.
  1623  	// given
  1624  	voterWithELS1 := vgrand.RandomStr(5)
  1625  
  1626  	// setup
  1627  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1628  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1629  
  1630  	// expect
  1631  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
  1632  
  1633  	// when
  1634  	err = eng.addNoVote(t, voterWithELS1, proposal.ID)
  1635  
  1636  	// then
  1637  	require.NoError(t, err)
  1638  
  1639  	// Vote 'NO' with 0.5 of equity-like share.
  1640  	// given
  1641  	voterWithELS2 := vgrand.RandomStr(5)
  1642  
  1643  	// setup
  1644  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1645  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1646  
  1647  	// expect
  1648  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
  1649  
  1650  	// when
  1651  	err = eng.addNoVote(t, voterWithELS2, proposal.ID)
  1652  
  1653  	// then
  1654  	require.NoError(t, err)
  1655  
  1656  	// Close the proposal.
  1657  	// given
  1658  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1659  
  1660  	// setup
  1661  	eng.ensureStakingAssetTotalSupply(t, 13)
  1662  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1663  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1664  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1665  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1666  
  1667  	// expect
  1668  	eng.expectPassedProposalEvent(t, proposal.ID)
  1669  	eng.expectVoteEvents(t)
  1670  	eng.expectGetMarketState(t, proposal.ID)
  1671  
  1672  	// when
  1673  	eng.OnTick(context.Background(), afterClosing)
  1674  }
  1675  
  1676  func testVotingWithMajorityOfNoFromTokenHoldersMakesMarketUpdateProposalDeclined(t *testing.T) {
  1677  	eng := getTestEngine(t, time.Now())
  1678  
  1679  	// Submit proposal.
  1680  	// given
  1681  	proposer := vgrand.RandomStr(5)
  1682  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1683  	marketID := proposal.MarketUpdate().MarketID
  1684  
  1685  	// setup
  1686  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1687  	eng.ensureExistingMarket(t, marketID)
  1688  	eng.ensureGetMarketFuture(t, marketID)
  1689  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1690  	eng.ensureAllAssetEnabled(t)
  1691  
  1692  	// expect
  1693  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1694  
  1695  	// when
  1696  	_, err := eng.submitProposal(t, proposal)
  1697  
  1698  	// then
  1699  	require.NoError(t, err)
  1700  
  1701  	// Vote 'NO' with 10 tokens.
  1702  	// given
  1703  	voterWithToken1 := vgrand.RandomStr(5)
  1704  
  1705  	// setup
  1706  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1707  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
  1708  
  1709  	// expect
  1710  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
  1711  
  1712  	// when
  1713  	err = eng.addNoVote(t, voterWithToken1, proposal.ID)
  1714  
  1715  	// then
  1716  	require.NoError(t, err)
  1717  
  1718  	// Vote 'YES' with 2 tokens.
  1719  	// given
  1720  	voterWithToken2 := vgrand.RandomStr(5)
  1721  
  1722  	// setup
  1723  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1724  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
  1725  
  1726  	// expect
  1727  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
  1728  
  1729  	// then
  1730  	err = eng.addYesVote(t, voterWithToken2, proposal.ID)
  1731  
  1732  	// then
  1733  	require.NoError(t, err)
  1734  
  1735  	// Vote 'YES' with 0.1 of equity-like share.
  1736  	// given
  1737  	voterWithELS1 := vgrand.RandomStr(5)
  1738  
  1739  	// setup
  1740  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1741  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1742  
  1743  	// expect
  1744  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
  1745  
  1746  	// when
  1747  	err = eng.addYesVote(t, voterWithELS1, proposal.ID)
  1748  
  1749  	// then
  1750  	require.NoError(t, err)
  1751  
  1752  	// Vote 'YES' with 0.5 of equity-like share.
  1753  	// given
  1754  	voterWithELS2 := vgrand.RandomStr(5)
  1755  
  1756  	// setup
  1757  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1758  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1759  
  1760  	// expect
  1761  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
  1762  
  1763  	// when
  1764  	err = eng.addYesVote(t, voterWithELS2, proposal.ID)
  1765  
  1766  	// then
  1767  	require.NoError(t, err)
  1768  
  1769  	// Close the proposal.
  1770  	// given
  1771  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1772  
  1773  	// setup
  1774  	eng.ensureStakingAssetTotalSupply(t, 13)
  1775  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
  1776  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
  1777  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1778  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1779  
  1780  	// expect
  1781  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached)
  1782  	eng.expectVoteEvents(t)
  1783  	eng.expectGetMarketState(t, proposal.ID)
  1784  
  1785  	// when
  1786  	eng.OnTick(context.Background(), afterClosing)
  1787  }
  1788  
  1789  func testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesMarketUpdateProposalPassed(t *testing.T) {
  1790  	eng := getTestEngine(t, time.Now())
  1791  
  1792  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
  1793  
  1794  	// Submit proposal.
  1795  	// given
  1796  	proposer := vgrand.RandomStr(5)
  1797  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1798  	marketID := proposal.MarketUpdate().MarketID
  1799  
  1800  	// setup
  1801  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1802  	eng.ensureExistingMarket(t, marketID)
  1803  	eng.ensureGetMarketFuture(t, marketID)
  1804  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1805  	eng.ensureAllAssetEnabled(t)
  1806  
  1807  	// expect
  1808  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1809  
  1810  	// when
  1811  	_, err := eng.submitProposal(t, proposal)
  1812  
  1813  	// then
  1814  	require.NoError(t, err)
  1815  
  1816  	// Vote 'NO' with 2 tokens.
  1817  	// given
  1818  	voterWithToken := vgrand.RandomStr(5)
  1819  
  1820  	// setup
  1821  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
  1822  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1823  
  1824  	// expect
  1825  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
  1826  
  1827  	// when
  1828  	err = eng.addNoVote(t, voterWithToken, proposal.ID)
  1829  
  1830  	// then
  1831  	require.NoError(t, err)
  1832  
  1833  	// Vote 'NO' with 0.1 of equity-like share.
  1834  	// given
  1835  	voterWithELS1 := vgrand.RandomStr(5)
  1836  
  1837  	// setup
  1838  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1839  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1840  
  1841  	// expect
  1842  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
  1843  
  1844  	// when
  1845  	err = eng.addNoVote(t, voterWithELS1, proposal.ID)
  1846  
  1847  	// then
  1848  	require.NoError(t, err)
  1849  
  1850  	// Vote 'YES' with 0.5 of equity-like share.
  1851  	// given
  1852  	voterWithELS2 := vgrand.RandomStr(5)
  1853  
  1854  	// setup
  1855  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1856  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1857  
  1858  	// expect
  1859  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
  1860  
  1861  	// when
  1862  	err = eng.addYesVote(t, voterWithELS2, proposal.ID)
  1863  
  1864  	// then
  1865  	require.NoError(t, err)
  1866  
  1867  	// Close the proposal.
  1868  	// given
  1869  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1870  
  1871  	// setup
  1872  	eng.ensureStakingAssetTotalSupply(t, 13)
  1873  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
  1874  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1875  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1876  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1877  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1878  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1879  
  1880  	// expect
  1881  	eng.expectPassedProposalEvent(t, proposal.ID)
  1882  	eng.expectVoteEvents(t)
  1883  	eng.expectGetMarketState(t, proposal.ID)
  1884  
  1885  	// when
  1886  	eng.OnTick(context.Background(), afterClosing)
  1887  }
  1888  
  1889  func testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesMarketUpdateProposalDeclined(t *testing.T) {
  1890  	eng := getTestEngine(t, time.Now())
  1891  
  1892  	// Submit proposal.
  1893  	// given
  1894  
  1895  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
  1896  
  1897  	proposer := vgrand.RandomStr(5)
  1898  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1899  	marketID := proposal.MarketUpdate().MarketID
  1900  
  1901  	// setup
  1902  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
  1903  	eng.ensureExistingMarket(t, marketID)
  1904  	eng.ensureGetMarketFuture(t, marketID)
  1905  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1906  	eng.ensureAllAssetEnabled(t)
  1907  
  1908  	// expect
  1909  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1910  
  1911  	// when
  1912  	_, err := eng.submitProposal(t, proposal)
  1913  
  1914  	// then
  1915  	require.NoError(t, err)
  1916  
  1917  	// Vote 'YES' with 2 tokens.
  1918  	// given
  1919  	voterWithToken := vgrand.RandomStr(5)
  1920  
  1921  	// setup
  1922  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
  1923  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1924  
  1925  	// expect
  1926  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
  1927  
  1928  	// when
  1929  	err = eng.addYesVote(t, voterWithToken, proposal.ID)
  1930  
  1931  	// then
  1932  	require.NoError(t, err)
  1933  
  1934  	// Vote 'YES' with 0.1 of equity-like share.
  1935  	// given
  1936  	voterWithELS1 := vgrand.RandomStr(5)
  1937  
  1938  	// setup
  1939  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1940  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1941  
  1942  	// expect
  1943  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
  1944  
  1945  	// when
  1946  	err = eng.addYesVote(t, voterWithELS1, proposal.ID)
  1947  
  1948  	// then
  1949  	require.NoError(t, err)
  1950  
  1951  	// Vote 'NO' with 0.5 of equity-like share.
  1952  	// given
  1953  	voterWithELS2 := vgrand.RandomStr(5)
  1954  
  1955  	// setup
  1956  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1957  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1958  
  1959  	// expect
  1960  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
  1961  
  1962  	// when
  1963  	err = eng.addNoVote(t, voterWithELS2, proposal.ID)
  1964  
  1965  	// then
  1966  	require.NoError(t, err)
  1967  
  1968  	// Close the proposal.
  1969  	// given
  1970  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1971  
  1972  	// setup
  1973  	eng.ensureStakingAssetTotalSupply(t, 13)
  1974  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
  1975  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
  1976  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
  1977  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
  1978  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
  1979  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
  1980  
  1981  	// ensure setting again the values have no effect
  1982  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0")
  1983  
  1984  	// expect
  1985  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached)
  1986  	eng.expectVoteEvents(t)
  1987  	eng.expectGetMarketState(t, proposal.ID)
  1988  
  1989  	// when
  1990  	eng.OnTick(context.Background(), afterClosing)
  1991  }
  1992  
  1993  func TestSubmitProposalWithInconsistentProductFails(t *testing.T) {
  1994  	eng := getTestEngine(t, time.Now())
  1995  
  1996  	// given
  1997  	proposer := vgrand.RandomStr(5)
  1998  	proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true)
  1999  	marketID := proposal.MarketUpdate().MarketID
  2000  
  2001  	// setup
  2002  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
  2003  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
  2004  
  2005  	// setup market will be a perpetual being updated to a future
  2006  	eng.ensureExistingMarket(t, marketID)
  2007  	eng.ensureGetMarketPerpetual(t, marketID)
  2008  
  2009  	// expect
  2010  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct)
  2011  
  2012  	// when
  2013  	toSubmit, err := eng.submitProposal(t, proposal)
  2014  
  2015  	// then
  2016  	require.ErrorIs(t, err, governance.ErrUpdateMarketDifferentProduct)
  2017  	require.Nil(t, toSubmit)
  2018  }