code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_update_spot_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  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/governance"
    24  	"code.vegaprotocol.io/vega/core/netparams"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestProposalForSpotMarketUpdate(t *testing.T) {
    33  	t.Run("Submitting a proposal for spot market update succeeds", testSubmittingProposalForSpotMarketUpdateSucceeds)
    34  	t.Run("Submitting a proposal for market update on unknown spot market fails", testSubmittingProposalForMarketUpdateForUnknownSpotMarketFails)
    35  
    36  	t.Run("Submitting a proposal for market update for not-enacted market fails", testSubmittingProposalForSpotMarketUpdateForNotEnactedMarketFails)
    37  	t.Run("Submitting a proposal for spot market update with insufficient equity-like share fails", testSubmittingProposalForSpotMarketUpdateWithInsufficientEquityLikeShareFails)
    38  	t.Run("Pre-enactment of spot market update proposal succeeds", testPreEnactmentOfSpotMarketUpdateSucceeds)
    39  
    40  	t.Run("Rejecting a proposal for market update succeeds", testRejectingProposalForSpotMarketUpdateSucceeds)
    41  
    42  	t.Run("Voting without reaching minimum of tokens and equity-like shares makes the spot market update proposal declined", testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesSpotMarketUpdateProposalPassed)
    43  	t.Run("Voting with a majority of 'yes' from tokens makes the spot market update proposal passed", testVotingWithMajorityOfYesFromTokenHoldersMakesSpotMarketUpdateProposalPassed)
    44  	t.Run("Voting with a majority of 'no' from tokens makes the spot market update proposal declined", testVotingWithMajorityOfNoFromTokenHoldersMakesSpotMarketUpdateProposalDeclined)
    45  	t.Run("Voting without reaching minimum of tokens and a majority of 'yes' from equity-like shares makes the spot market update proposal passed", testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesSpotMarketUpdateProposalPassed)
    46  	t.Run("Voting without reaching minimum of tokens and a majority of 'no' from equity-like shares makes the spot market update proposal declined", testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesSpotMarketUpdateProposalDeclined)
    47  }
    48  
    49  func testSubmittingProposalForSpotMarketUpdateSucceeds(t *testing.T) {
    50  	eng := getTestEngine(t, time.Now())
    51  
    52  	// given
    53  	proposer := vgrand.RandomStr(5)
    54  	proposal := eng.newProposalForSpotMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow())
    55  	marketID := proposal.SpotMarketUpdate().MarketID
    56  
    57  	// setup
    58  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
    59  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
    60  	eng.ensureExistingMarket(t, marketID)
    61  	eng.ensureGetMarketSpot(t, marketID)
    62  
    63  	// expect
    64  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
    65  
    66  	// when
    67  	toSubmit, err := eng.submitProposal(t, proposal)
    68  
    69  	// then
    70  	require.NoError(t, err)
    71  	require.NotNil(t, toSubmit)
    72  }
    73  
    74  func testSubmittingProposalForMarketUpdateForUnknownSpotMarketFails(t *testing.T) {
    75  	eng := getTestEngine(t, time.Now())
    76  
    77  	// given
    78  	proposer := vgrand.RandomStr(5)
    79  	proposal := eng.newProposalForSpotMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow())
    80  	marketID := proposal.SpotMarketUpdate().MarketID
    81  
    82  	// setup
    83  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
    84  	eng.ensureNonExistingMarket(t, marketID)
    85  
    86  	// expect
    87  	eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket)
    88  
    89  	// when
    90  	toSubmit, err := eng.submitProposal(t, proposal)
    91  
    92  	// then
    93  	require.ErrorIs(t, governance.ErrMarketDoesNotExist, err)
    94  	require.Nil(t, toSubmit)
    95  }
    96  
    97  func testSubmittingProposalForSpotMarketUpdateForNotEnactedMarketFails(t *testing.T) {
    98  	eng := getTestEngine(t, time.Now())
    99  
   100  	// given
   101  	proposer := vgrand.RandomStr(5)
   102  	newMarketProposal := eng.newProposalForNewSpotMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour))
   103  	marketID := newMarketProposal.ID
   104  
   105  	// setup
   106  	eng.ensureAllAssetEnabled(t)
   107  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
   108  	eng.expectOpenProposalEvent(t, proposer, marketID)
   109  
   110  	// when
   111  	toSubmit, err := eng.submitProposal(t, newMarketProposal)
   112  
   113  	// then
   114  	require.NoError(t, err)
   115  	require.NotNil(t, toSubmit)
   116  	assert.True(t, toSubmit.IsNewSpotMarket())
   117  
   118  	// given
   119  	updateMarketProposal := eng.newProposalForSpotMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow())
   120  	updateMarketProposal.SpotMarketUpdate().MarketID = marketID
   121  
   122  	// setup
   123  	eng.ensureTokenBalanceForParty(t, proposer, 123456789)
   124  	eng.ensureExistingMarket(t, marketID)
   125  
   126  	// expect
   127  	eng.expectRejectedProposalEvent(t, proposer, updateMarketProposal.ID, types.ProposalErrorInvalidMarket)
   128  
   129  	// when
   130  	toSubmit, err = eng.submitProposal(t, updateMarketProposal)
   131  
   132  	// then
   133  	require.ErrorIs(t, governance.ErrMarketProposalStillOpen, err)
   134  	require.Nil(t, toSubmit)
   135  
   136  	// now the original market proposal passes
   137  	// given
   138  	voter1 := vgrand.RandomStr(5)
   139  	eng.ensureTokenBalanceForParty(t, voter1, 7)
   140  	eng.expectVoteEvent(t, voter1, marketID)
   141  	err = eng.addYesVote(t, voter1, marketID)
   142  	require.NoError(t, err)
   143  
   144  	afterClosing := time.Unix(newMarketProposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   145  	eng.ensureStakingAssetTotalSupply(t, 10)
   146  	eng.ensureTokenBalanceForParty(t, voter1, 7)
   147  	eng.expectPassedProposalEvent(t, marketID)
   148  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7")
   149  	eng.expectGetMarketState(t, marketID)
   150  	eng.OnTick(context.Background(), afterClosing)
   151  
   152  	// submitting now the market proposal has passed should work
   153  	eng.ensureTokenBalanceForParty(t, proposer, 1000)
   154  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   155  	eng.ensureExistingMarket(t, marketID)
   156  	eng.ensureGetMarketFuture(t, marketID)
   157  	eng.expectOpenProposalEvent(t, proposer, updateMarketProposal.ID)
   158  	toSubmit, err = eng.submitProposal(t, updateMarketProposal)
   159  	require.NoError(t, err)
   160  	require.NotNil(t, toSubmit)
   161  }
   162  
   163  func testSubmittingProposalForSpotMarketUpdateWithInsufficientEquityLikeShareFails(t *testing.T) {
   164  	eng := getTestEngine(t, time.Now())
   165  
   166  	// given
   167  	party := vgrand.RandomStr(5)
   168  	proposal := eng.newProposalForSpotMarketUpdate("״market-1", party, eng.tsvc.GetTimeNow())
   169  	marketID := proposal.SpotMarketUpdate().MarketID
   170  
   171  	// setup
   172  	// eng.ensureTokenBalanceForParty(t, party, 100)
   173  	eng.ensureExistingMarket(t, marketID)
   174  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.05)
   175  
   176  	// expect
   177  	eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientTokens)
   178  
   179  	// when
   180  	toSubmit, err := eng.submitProposal(t, proposal)
   181  
   182  	// then
   183  	require.Error(t, err)
   184  	assert.Contains(t, err.Error(), "no balance for party")
   185  	require.Nil(t, toSubmit)
   186  }
   187  
   188  func testPreEnactmentOfSpotMarketUpdateSucceeds(t *testing.T) {
   189  	eng := getTestEngine(t, time.Now())
   190  
   191  	// Submit proposal.
   192  	// given
   193  	proposer := vgrand.RandomStr(5)
   194  	proposal := eng.newProposalForSpotMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow())
   195  	marketID := proposal.SpotMarketUpdate().MarketID
   196  
   197  	// setup
   198  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   199  	eng.ensureExistingMarket(t, marketID)
   200  	eng.ensureGetMarketSpot(t, marketID)
   201  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   202  	eng.ensureAllAssetEnabled(t)
   203  
   204  	// expect
   205  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   206  
   207  	// when
   208  	_, err := eng.submitProposal(t, proposal)
   209  
   210  	// then
   211  	require.NoError(t, err)
   212  
   213  	// Vote 'YES' with 10 tokens.
   214  	// given
   215  	voterWithToken1 := vgrand.RandomStr(5)
   216  
   217  	// setup
   218  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   219  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
   220  
   221  	// expect
   222  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
   223  
   224  	// when
   225  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
   226  
   227  	// then
   228  	require.NoError(t, err)
   229  
   230  	// Vote 'NO' with 2 tokens.
   231  	// given
   232  	voterWithToken2 := vgrand.RandomStr(5)
   233  
   234  	// setup
   235  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   236  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
   237  
   238  	// expect
   239  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
   240  
   241  	// then
   242  	err = eng.addNoVote(t, voterWithToken2, proposal.ID)
   243  
   244  	// then
   245  	require.NoError(t, err)
   246  
   247  	// Close the proposal.
   248  	// given
   249  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   250  
   251  	// setup
   252  	eng.ensureStakingAssetTotalSupply(t, 13)
   253  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   254  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   255  
   256  	// expect
   257  	eng.expectPassedProposalEvent(t, proposal.ID)
   258  	eng.expectVoteEvents(t)
   259  	eng.expectGetMarketState(t, marketID)
   260  
   261  	// when
   262  	eng.OnTick(context.Background(), afterClosing)
   263  
   264  	// Enact the proposal.
   265  	// given
   266  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
   267  	existingMarket := types.Market{
   268  		ID: marketID,
   269  		TradableInstrument: &types.TradableInstrument{
   270  			Instrument: &types.Instrument{
   271  				Name: vgrand.RandomStr(10),
   272  				Product: &types.InstrumentSpot{
   273  					Spot: &types.Spot{
   274  						Name:       "BTC/USDT",
   275  						BaseAsset:  "BTC",
   276  						QuoteAsset: "USDT",
   277  					},
   278  				},
   279  			},
   280  		},
   281  		DecimalPlaces:         3,
   282  		PositionDecimalPlaces: 4,
   283  		OpeningAuction: &types.AuctionDuration{
   284  			Duration: 42,
   285  		},
   286  	}
   287  
   288  	// setup
   289  	eng.ensureGetMarket(t, marketID, existingMarket)
   290  
   291  	// when
   292  	enacted, _ := eng.OnTick(context.Background(), afterEnactment)
   293  
   294  	// then
   295  	require.NotEmpty(t, enacted)
   296  	require.True(t, enacted[0].IsUpdateSpotMarket())
   297  	updatedMarket := enacted[0].UpdateSpotMarket()
   298  	assert.Equal(t, existingMarket.ID, updatedMarket.ID)
   299  	assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentSpot).Spot.BaseAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentSpot).Spot.BaseAsset)
   300  	assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentSpot).Spot.QuoteAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentSpot).Spot.QuoteAsset)
   301  	assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces)
   302  	assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces)
   303  	assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration)
   304  }
   305  
   306  func testRejectingProposalForSpotMarketUpdateSucceeds(t *testing.T) {
   307  	eng := getTestEngine(t, time.Now())
   308  
   309  	// given
   310  	party := vgrand.RandomStr(5)
   311  	proposal := eng.newProposalForSpotMarketUpdate("market-1", party, eng.tsvc.GetTimeNow())
   312  	marketID := proposal.SpotMarketUpdate().MarketID
   313  
   314  	// setup
   315  	eng.ensureAllAssetEnabled(t)
   316  	eng.ensureExistingMarket(t, marketID)
   317  	eng.ensureGetMarketSpot(t, marketID)
   318  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.7)
   319  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1")
   320  	eng.ensureTokenBalanceForParty(t, party, 10000)
   321  
   322  	// expect
   323  	eng.expectOpenProposalEvent(t, party, proposal.ID)
   324  
   325  	// when
   326  	toSubmit, err := eng.submitProposal(t, proposal)
   327  
   328  	// then
   329  	require.NoError(t, err)
   330  	require.NotNil(t, toSubmit)
   331  
   332  	// expect
   333  	eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorCouldNotInstantiateMarket)
   334  
   335  	// when
   336  	err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError)
   337  
   338  	// then
   339  	require.NoError(t, err)
   340  
   341  	// when
   342  	// Just one more time to make sure it was removed from proposals.
   343  	err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError)
   344  
   345  	// then
   346  	assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error())
   347  }
   348  
   349  func testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesSpotMarketUpdateProposalPassed(t *testing.T) {
   350  	eng := getTestEngine(t, time.Now())
   351  
   352  	// Submit proposal.
   353  	// given
   354  	proposer := vgrand.RandomStr(5)
   355  	proposal := eng.newProposalForSpotMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow())
   356  	marketID := proposal.SpotMarketUpdate().MarketID
   357  
   358  	// setup
   359  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
   360  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipationLP, "0.5")
   361  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1)
   362  	eng.ensureExistingMarket(t, marketID)
   363  	eng.ensureGetMarketSpot(t, marketID)
   364  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   365  	eng.ensureAllAssetEnabled(t)
   366  
   367  	// expect
   368  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   369  
   370  	// when
   371  	_, err := eng.submitProposal(t, proposal)
   372  
   373  	// then
   374  	require.NoError(t, err)
   375  
   376  	// Vote using a token holder without equity-like share.
   377  	// when
   378  	voterWithToken := vgrand.RandomStr(5)
   379  
   380  	// setup
   381  	eng.ensureTokenBalanceForParty(t, voterWithToken, 1)
   382  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   383  
   384  	// expect
   385  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
   386  
   387  	// when
   388  	err = eng.addYesVote(t, voterWithToken, proposal.ID)
   389  
   390  	// then
   391  	require.NoError(t, err)
   392  
   393  	// Vote using equity-like share holder without tokens.
   394  	// given
   395  	voterWithELS := vgrand.RandomStr(5)
   396  
   397  	// setup
   398  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
   399  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1)
   400  
   401  	// expect
   402  	eng.expectVoteEvent(t, voterWithELS, proposal.ID)
   403  
   404  	// when
   405  	err = eng.addNoVote(t, voterWithELS, proposal.ID)
   406  
   407  	// then
   408  	require.NoError(t, err)
   409  
   410  	// Closing the proposal.
   411  	// given
   412  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   413  
   414  	// setup
   415  	eng.ensureStakingAssetTotalSupply(t, 10)
   416  	eng.ensureTokenBalanceForParty(t, voterWithToken, 1)
   417  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   418  	eng.ensureTokenBalanceForParty(t, voterWithELS, 0)
   419  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1)
   420  
   421  	// expect
   422  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached)
   423  	eng.expectVoteEvents(t)
   424  	eng.expectGetMarketState(t, proposal.ID)
   425  
   426  	// when
   427  	eng.OnTick(context.Background(), afterClosing)
   428  }
   429  
   430  func testVotingWithMajorityOfYesFromTokenHoldersMakesSpotMarketUpdateProposalPassed(t *testing.T) {
   431  	eng := getTestEngine(t, time.Now())
   432  
   433  	// Submit proposal.
   434  	// given
   435  	proposer := vgrand.RandomStr(5)
   436  	proposal := eng.newProposalForSpotMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow())
   437  	marketID := proposal.SpotMarketUpdate().MarketID
   438  
   439  	// setup
   440  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   441  	eng.ensureExistingMarket(t, marketID)
   442  	eng.ensureGetMarketSpot(t, marketID)
   443  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   444  	eng.ensureAllAssetEnabled(t)
   445  
   446  	// expect
   447  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   448  
   449  	// when
   450  	_, err := eng.submitProposal(t, proposal)
   451  
   452  	// then
   453  	require.NoError(t, err)
   454  
   455  	// Vote 'YES' with 10 tokens.
   456  	// given
   457  	voterWithToken1 := vgrand.RandomStr(5)
   458  
   459  	// setup
   460  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   461  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
   462  
   463  	// expect
   464  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
   465  
   466  	// when
   467  	err = eng.addYesVote(t, voterWithToken1, proposal.ID)
   468  
   469  	// then
   470  	require.NoError(t, err)
   471  
   472  	// Vote 'NO' with 2 tokens.
   473  	// given
   474  	voterWithToken2 := vgrand.RandomStr(5)
   475  
   476  	// setup
   477  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   478  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
   479  
   480  	// expect
   481  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
   482  
   483  	// then
   484  	err = eng.addNoVote(t, voterWithToken2, proposal.ID)
   485  
   486  	// then
   487  	require.NoError(t, err)
   488  
   489  	// Vote 'NO' with 0.1 of equity-like share.
   490  	// given
   491  	voterWithELS1 := vgrand.RandomStr(5)
   492  
   493  	// setup
   494  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   495  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   496  
   497  	// expect
   498  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
   499  
   500  	// when
   501  	err = eng.addNoVote(t, voterWithELS1, proposal.ID)
   502  
   503  	// then
   504  	require.NoError(t, err)
   505  
   506  	// Vote 'NO' with 0.5 of equity-like share.
   507  	// given
   508  	voterWithELS2 := vgrand.RandomStr(5)
   509  
   510  	// setup
   511  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   512  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   513  
   514  	// expect
   515  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
   516  
   517  	// when
   518  	err = eng.addNoVote(t, voterWithELS2, proposal.ID)
   519  
   520  	// then
   521  	require.NoError(t, err)
   522  
   523  	// Close the proposal.
   524  	// given
   525  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   526  
   527  	// setup
   528  	eng.ensureStakingAssetTotalSupply(t, 13)
   529  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   530  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   531  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   532  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   533  
   534  	// expect
   535  	eng.expectPassedProposalEvent(t, proposal.ID)
   536  	eng.expectVoteEvents(t)
   537  	eng.expectGetMarketState(t, proposal.ID)
   538  
   539  	// when
   540  	eng.OnTick(context.Background(), afterClosing)
   541  }
   542  
   543  func testVotingWithMajorityOfNoFromTokenHoldersMakesSpotMarketUpdateProposalDeclined(t *testing.T) {
   544  	eng := getTestEngine(t, time.Now())
   545  
   546  	// Submit proposal.
   547  	// given
   548  	proposer := vgrand.RandomStr(5)
   549  	proposal := eng.newProposalForSpotMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow())
   550  	marketID := proposal.SpotMarketUpdate().MarketID
   551  
   552  	// setup
   553  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   554  	eng.ensureExistingMarket(t, marketID)
   555  	eng.ensureGetMarketSpot(t, marketID)
   556  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   557  	eng.ensureAllAssetEnabled(t)
   558  
   559  	// expect
   560  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   561  
   562  	// when
   563  	_, err := eng.submitProposal(t, proposal)
   564  
   565  	// then
   566  	require.NoError(t, err)
   567  
   568  	// Vote 'NO' with 10 tokens.
   569  	// given
   570  	voterWithToken1 := vgrand.RandomStr(5)
   571  
   572  	// setup
   573  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   574  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0)
   575  
   576  	// expect
   577  	eng.expectVoteEvent(t, voterWithToken1, proposal.ID)
   578  
   579  	// when
   580  	err = eng.addNoVote(t, voterWithToken1, proposal.ID)
   581  
   582  	// then
   583  	require.NoError(t, err)
   584  
   585  	// Vote 'YES' with 2 tokens.
   586  	// given
   587  	voterWithToken2 := vgrand.RandomStr(5)
   588  
   589  	// setup
   590  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   591  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0)
   592  
   593  	// expect
   594  	eng.expectVoteEvent(t, voterWithToken2, proposal.ID)
   595  
   596  	// then
   597  	err = eng.addYesVote(t, voterWithToken2, proposal.ID)
   598  
   599  	// then
   600  	require.NoError(t, err)
   601  
   602  	// Vote 'YES' with 0.1 of equity-like share.
   603  	// given
   604  	voterWithELS1 := vgrand.RandomStr(5)
   605  
   606  	// setup
   607  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   608  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   609  
   610  	// expect
   611  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
   612  
   613  	// when
   614  	err = eng.addYesVote(t, voterWithELS1, proposal.ID)
   615  
   616  	// then
   617  	require.NoError(t, err)
   618  
   619  	// Vote 'YES' with 0.5 of equity-like share.
   620  	// given
   621  	voterWithELS2 := vgrand.RandomStr(5)
   622  
   623  	// setup
   624  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   625  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   626  
   627  	// expect
   628  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
   629  
   630  	// when
   631  	err = eng.addYesVote(t, voterWithELS2, proposal.ID)
   632  
   633  	// then
   634  	require.NoError(t, err)
   635  
   636  	// Close the proposal.
   637  	// given
   638  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   639  
   640  	// setup
   641  	eng.ensureStakingAssetTotalSupply(t, 13)
   642  	eng.ensureTokenBalanceForParty(t, voterWithToken1, 10)
   643  	eng.ensureTokenBalanceForParty(t, voterWithToken2, 2)
   644  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   645  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   646  
   647  	// expect
   648  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached)
   649  	eng.expectVoteEvents(t)
   650  	eng.expectGetMarketState(t, proposal.ID)
   651  
   652  	// when
   653  	eng.OnTick(context.Background(), afterClosing)
   654  }
   655  
   656  func testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesSpotMarketUpdateProposalPassed(t *testing.T) {
   657  	eng := getTestEngine(t, time.Now())
   658  
   659  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
   660  
   661  	// Submit proposal.
   662  	// given
   663  	proposer := vgrand.RandomStr(5)
   664  	proposal := eng.newProposalForSpotMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow())
   665  	marketID := proposal.SpotMarketUpdate().MarketID
   666  
   667  	// setup
   668  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   669  	eng.ensureExistingMarket(t, marketID)
   670  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   671  	eng.ensureAllAssetEnabled(t)
   672  	eng.ensureGetMarketSpot(t, marketID)
   673  
   674  	// expect
   675  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   676  
   677  	// when
   678  	_, err := eng.submitProposal(t, proposal)
   679  
   680  	// then
   681  	require.NoError(t, err)
   682  
   683  	// Vote 'NO' with 2 tokens.
   684  	// given
   685  	voterWithToken := vgrand.RandomStr(5)
   686  
   687  	// setup
   688  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
   689  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   690  
   691  	// expect
   692  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
   693  
   694  	// when
   695  	err = eng.addNoVote(t, voterWithToken, proposal.ID)
   696  
   697  	// then
   698  	require.NoError(t, err)
   699  
   700  	// Vote 'NO' with 0.1 of equity-like share.
   701  	// given
   702  	voterWithELS1 := vgrand.RandomStr(5)
   703  
   704  	// setup
   705  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   706  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   707  
   708  	// expect
   709  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
   710  
   711  	// when
   712  	err = eng.addNoVote(t, voterWithELS1, proposal.ID)
   713  
   714  	// then
   715  	require.NoError(t, err)
   716  
   717  	// Vote 'YES' with 0.5 of equity-like share.
   718  	// given
   719  	voterWithELS2 := vgrand.RandomStr(5)
   720  
   721  	// setup
   722  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   723  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   724  
   725  	// expect
   726  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
   727  
   728  	// when
   729  	err = eng.addYesVote(t, voterWithELS2, proposal.ID)
   730  
   731  	// then
   732  	require.NoError(t, err)
   733  
   734  	// Close the proposal.
   735  	// given
   736  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   737  
   738  	// setup
   739  	eng.ensureStakingAssetTotalSupply(t, 13)
   740  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
   741  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   742  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   743  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   744  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   745  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   746  
   747  	// expect
   748  	eng.expectPassedProposalEvent(t, proposal.ID)
   749  	eng.expectVoteEvents(t)
   750  	eng.expectGetMarketState(t, proposal.ID)
   751  
   752  	// when
   753  	eng.OnTick(context.Background(), afterClosing)
   754  }
   755  
   756  func testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesSpotMarketUpdateProposalDeclined(t *testing.T) {
   757  	eng := getTestEngine(t, time.Now())
   758  
   759  	// Submit proposal.
   760  	// given
   761  
   762  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5")
   763  
   764  	proposer := vgrand.RandomStr(5)
   765  	proposal := eng.newProposalForSpotMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow())
   766  	marketID := proposal.SpotMarketUpdate().MarketID
   767  
   768  	// setup
   769  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7)
   770  	eng.ensureExistingMarket(t, marketID)
   771  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   772  	eng.ensureAllAssetEnabled(t)
   773  	eng.ensureGetMarketSpot(t, marketID)
   774  
   775  	// expect
   776  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   777  
   778  	// when
   779  	_, err := eng.submitProposal(t, proposal)
   780  
   781  	// then
   782  	require.NoError(t, err)
   783  
   784  	// Vote 'YES' with 2 tokens.
   785  	// given
   786  	voterWithToken := vgrand.RandomStr(5)
   787  
   788  	// setup
   789  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
   790  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   791  
   792  	// expect
   793  	eng.expectVoteEvent(t, voterWithToken, proposal.ID)
   794  
   795  	// when
   796  	err = eng.addYesVote(t, voterWithToken, proposal.ID)
   797  
   798  	// then
   799  	require.NoError(t, err)
   800  
   801  	// Vote 'YES' with 0.1 of equity-like share.
   802  	// given
   803  	voterWithELS1 := vgrand.RandomStr(5)
   804  
   805  	// setup
   806  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   807  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   808  
   809  	// expect
   810  	eng.expectVoteEvent(t, voterWithELS1, proposal.ID)
   811  
   812  	// when
   813  	err = eng.addYesVote(t, voterWithELS1, proposal.ID)
   814  
   815  	// then
   816  	require.NoError(t, err)
   817  
   818  	// Vote 'NO' with 0.5 of equity-like share.
   819  	// given
   820  	voterWithELS2 := vgrand.RandomStr(5)
   821  
   822  	// setup
   823  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   824  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   825  
   826  	// expect
   827  	eng.expectVoteEvent(t, voterWithELS2, proposal.ID)
   828  
   829  	// when
   830  	err = eng.addNoVote(t, voterWithELS2, proposal.ID)
   831  
   832  	// then
   833  	require.NoError(t, err)
   834  
   835  	// Close the proposal.
   836  	// given
   837  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
   838  
   839  	// setup
   840  	eng.ensureStakingAssetTotalSupply(t, 13)
   841  	eng.ensureTokenBalanceForParty(t, voterWithToken, 2)
   842  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0)
   843  	eng.ensureTokenBalanceForParty(t, voterWithELS1, 0)
   844  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1)
   845  	eng.ensureTokenBalanceForParty(t, voterWithELS2, 0)
   846  	eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7)
   847  
   848  	// ensure setting again the values have no effect
   849  	eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0")
   850  
   851  	// expect
   852  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached)
   853  	eng.expectVoteEvents(t)
   854  	eng.expectGetMarketState(t, proposal.ID)
   855  
   856  	// when
   857  	eng.OnTick(context.Background(), afterClosing)
   858  }