code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_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  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/assets"
    28  	"code.vegaprotocol.io/vega/core/assets/builtin"
    29  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    30  	"code.vegaprotocol.io/vega/core/datasource"
    31  	"code.vegaprotocol.io/vega/core/datasource/common"
    32  	dstypes "code.vegaprotocol.io/vega/core/datasource/common"
    33  	dsdefinition "code.vegaprotocol.io/vega/core/datasource/definition"
    34  	"code.vegaprotocol.io/vega/core/datasource/external/signedoracle"
    35  	"code.vegaprotocol.io/vega/core/events"
    36  	"code.vegaprotocol.io/vega/core/governance"
    37  	"code.vegaprotocol.io/vega/core/governance/mocks"
    38  	"code.vegaprotocol.io/vega/core/netparams"
    39  	"code.vegaprotocol.io/vega/core/types"
    40  	"code.vegaprotocol.io/vega/libs/num"
    41  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    42  	"code.vegaprotocol.io/vega/logging"
    43  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    44  
    45  	"github.com/golang/mock/gomock"
    46  	"github.com/stretchr/testify/assert"
    47  	"github.com/stretchr/testify/require"
    48  )
    49  
    50  var errNoBalanceForParty = errors.New("no balance for party")
    51  
    52  type tstEngine struct {
    53  	*governance.Engine
    54  	ctrl            *gomock.Controller
    55  	accounts        *mocks.MockStakingAccounts
    56  	tsvc            *mocks.MockTimeService
    57  	broker          *bmocks.MockBroker
    58  	witness         *mocks.MockWitness
    59  	markets         *mocks.MockMarkets
    60  	assets          *mocks.MockAssets
    61  	netp            *netparams.Store
    62  	proposalCounter uint                          // to streamline proposal generation
    63  	tokenBal        map[string]uint64             // party > balance
    64  	els             map[string]map[string]float64 // market > party > ELS
    65  }
    66  
    67  func TestSubmitProposals(t *testing.T) {
    68  	t.Run("Submitting a proposal with closing time too soon fails", testSubmittingProposalWithClosingTimeTooSoonFails)
    69  	t.Run("Submitting a proposal with internal time termination with closing time too soon fails", testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooSoonFails)
    70  	t.Run("Submitting a proposal with closing time too late fails", testSubmittingProposalWithClosingTimeTooLateFails)
    71  	t.Run("Submitting a proposal with internal time termination with closing time too late fails", testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooLateFails)
    72  	t.Run("Submitting a proposal with enactment time too soon fails", testSubmittingProposalWithEnactmentTimeTooSoonFails)
    73  	t.Run("Submitting a proposal with enactment time too late fails", testSubmittingProposalWithEnactmentTimeTooLateFails)
    74  	t.Run("Submitting a proposal with non-existing account fails", testSubmittingProposalWithNonExistingAccountFails)
    75  	t.Run("Submitting a proposal with internal time termination with non-existing account fails", testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails)
    76  	t.Run("Submitting a proposal without enough stake fails", testSubmittingProposalWithoutEnoughStakeFails)
    77  	t.Run("Submitting an update market proposal without enough stake and els fails", testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails)
    78  	t.Run("Submitting a proposal with internal time termination without enough stake fails", testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails)
    79  
    80  	t.Run("Submitting a time-triggered proposal for new market with termination time before enactment time fails", testSubmittingTimeTriggeredProposalNewMarketTerminationBeforeEnactmentFails)
    81  
    82  	t.Run("Voting on non-existing proposal fails", testVotingOnNonExistingProposalFails)
    83  	t.Run("Voting with non-existing account fails", testVotingWithNonExistingAccountFails)
    84  	t.Run("Voting without token fails", testVotingWithoutTokenFails)
    85  
    86  	t.Run("Test multiple proposal lifecycle", testMultipleProposalsLifecycle)
    87  	t.Run("Withdrawing vote assets removes vote from proposal state calculation", testWithdrawingVoteAssetRemovesVoteFromProposalStateCalculation)
    88  
    89  	t.Run("Updating voters key on votes succeeds", testUpdatingVotersKeyOnVotesSucceeds)
    90  	t.Run("Updating voters key on votes with internal time termination succeeds", testUpdatingVotersKeyOnVotesWithInternalTimeTerminationSucceeds)
    91  
    92  	t.Run("Computing the governance state hash is deterministic", testComputingGovernanceStateHashIsDeterministic)
    93  	t.Run("Submit proposal update market", testSubmitProposalMarketUpdate)
    94  }
    95  
    96  func testUpdatingVotersKeyOnVotesSucceeds(t *testing.T) {
    97  	eng := getTestEngine(t, time.Now())
    98  
    99  	// given
   100  	proposer := vgrand.RandomStr(5)
   101  	proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
   102  
   103  	// setup
   104  	eng.ensureAllAssetEnabled(t)
   105  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   106  
   107  	// expect
   108  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   109  
   110  	// when
   111  	_, err := eng.submitProposal(t, proposal)
   112  
   113  	// then
   114  	require.NoError(t, err)
   115  
   116  	// given
   117  	voter1 := vgrand.RandomStr(5)
   118  
   119  	// setup
   120  	eng.ensureTokenBalanceForParty(t, voter1, 1)
   121  
   122  	// expect
   123  	eng.expectVoteEvent(t, voter1, proposal.ID)
   124  
   125  	// when
   126  	err = eng.addYesVote(t, voter1, proposal.ID)
   127  
   128  	// then
   129  	require.NoError(t, err)
   130  
   131  	// given
   132  	voter2 := vgrand.RandomStr(5)
   133  
   134  	// setup
   135  	eng.ensureTokenBalanceForParty(t, voter2, 1)
   136  
   137  	// expect
   138  	eng.expectVoteEvent(t, voter2, proposal.ID)
   139  
   140  	// when
   141  	err = eng.addNoVote(t, voter2, proposal.ID)
   142  
   143  	// then
   144  	require.NoError(t, err)
   145  
   146  	// given
   147  	newVoter1ID := vgrand.RandomStr(5)
   148  
   149  	// expect
   150  	eng.expectVoteEvent(t, newVoter1ID, proposal.ID)
   151  
   152  	// then
   153  	eng.ValidatorKeyChanged(context.Background(), voter1, newVoter1ID)
   154  
   155  	// given
   156  	newVoter2ID := vgrand.RandomStr(5)
   157  
   158  	// setup
   159  	eng.expectVoteEvent(t, newVoter2ID, proposal.ID)
   160  
   161  	// then
   162  	eng.ValidatorKeyChanged(context.Background(), voter2, newVoter2ID)
   163  }
   164  
   165  func testUpdatingVotersKeyOnVotesWithInternalTimeTerminationSucceeds(t *testing.T) {
   166  	eng := getTestEngine(t, time.Now())
   167  
   168  	// given
   169  	proposer := vgrand.RandomStr(5)
   170  	proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false)
   171  
   172  	// setup
   173  	eng.ensureAllAssetEnabled(t)
   174  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   175  
   176  	// expect
   177  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
   178  
   179  	// when
   180  	_, err := eng.submitProposal(t, proposal)
   181  
   182  	// then
   183  	require.NoError(t, err)
   184  
   185  	// given
   186  	voter1 := vgrand.RandomStr(5)
   187  
   188  	// setup
   189  	eng.ensureTokenBalanceForParty(t, voter1, 1)
   190  
   191  	// expect
   192  	eng.expectVoteEvent(t, voter1, proposal.ID)
   193  
   194  	// when
   195  	err = eng.addYesVote(t, voter1, proposal.ID)
   196  
   197  	// then
   198  	require.NoError(t, err)
   199  
   200  	// given
   201  	voter2 := vgrand.RandomStr(5)
   202  
   203  	// setup
   204  	eng.ensureTokenBalanceForParty(t, voter2, 1)
   205  
   206  	// expect
   207  	eng.expectVoteEvent(t, voter2, proposal.ID)
   208  
   209  	// when
   210  	err = eng.addNoVote(t, voter2, proposal.ID)
   211  
   212  	// then
   213  	require.NoError(t, err)
   214  
   215  	// given
   216  	newVoter1ID := vgrand.RandomStr(5)
   217  
   218  	// expect
   219  	eng.expectVoteEvent(t, newVoter1ID, proposal.ID)
   220  
   221  	// then
   222  	eng.ValidatorKeyChanged(context.Background(), voter1, newVoter1ID)
   223  
   224  	// given
   225  	newVoter2ID := vgrand.RandomStr(5)
   226  
   227  	// setup
   228  	eng.expectVoteEvent(t, newVoter2ID, proposal.ID)
   229  
   230  	// then
   231  	eng.ValidatorKeyChanged(context.Background(), voter2, newVoter2ID)
   232  }
   233  
   234  func testSubmittingProposalWithNonExistingAccountFails(t *testing.T) {
   235  	eng := getTestEngine(t, time.Now())
   236  
   237  	// given
   238  	party := vgrand.RandomStr(5)
   239  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   240  
   241  	tcs := []struct {
   242  		name     string
   243  		proposal types.Proposal
   244  	}{
   245  		{
   246  			name:     "For new market",
   247  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, true),
   248  		}, {
   249  			name:     "For new asset",
   250  			proposal: eng.newProposalForNewAsset(party, now),
   251  		}, {
   252  			name:     "Freeform",
   253  			proposal: eng.newFreeformProposal(party, now),
   254  		},
   255  	}
   256  
   257  	for _, tc := range tcs {
   258  		t.Run(tc.name, func(tt *testing.T) {
   259  			// setup
   260  			eng.ensureAllAssetEnabled(tt)
   261  			eng.ensureNoAccountForParty(tt, party)
   262  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens)
   263  
   264  			// when
   265  			_, err := eng.submitProposal(tt, tc.proposal)
   266  
   267  			// then
   268  			require.Error(tt, err)
   269  			assert.EqualError(tt, err, errNoBalanceForParty.Error())
   270  		})
   271  
   272  		t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) {
   273  			// setup
   274  			eng.ensureAllAssetEnabled(tt)
   275  			eng.ensureNoAccountForParty(tt, party)
   276  
   277  			batchID := eng.newProposalID()
   278  
   279  			// expect
   280  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   281  			eng.expectProposalEvents(t, []expectedProposal{
   282  				{
   283  					partyID:    party,
   284  					proposalID: tc.proposal.ID,
   285  					state:      types.ProposalStateRejected,
   286  					reason:     types.ProposalErrorInsufficientTokens,
   287  				},
   288  			})
   289  
   290  			sub := eng.newBatchSubmission(
   291  				now.Add(48*time.Hour).Unix(),
   292  				tc.proposal,
   293  			)
   294  
   295  			// when
   296  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   297  
   298  			// then
   299  			require.Error(tt, err)
   300  			assert.EqualError(tt, err, errNoBalanceForParty.Error())
   301  		})
   302  	}
   303  }
   304  
   305  func testSubmitProposalMarketUpdate(t *testing.T) {
   306  	eng := getTestEngine(t, time.Now())
   307  
   308  	// given
   309  	party := vgrand.RandomStr(5)
   310  	marketID := vgrand.RandomStr(5)
   311  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   312  	tc := struct {
   313  		name     string
   314  		proposal types.Proposal
   315  	}{
   316  		name:     "For market update",
   317  		proposal: eng.newProposalForMarketUpdate(marketID, party, now, nil, nil, true),
   318  	}
   319  
   320  	// test that with no account but equity like share, a market update proposal goes through
   321  	t.Run(tc.name, func(tt *testing.T) {
   322  		// setup
   323  		eng.ensureAllAssetEnabled(tt)
   324  		eng.ensureNoAccountForParty(tt, party)
   325  		eng.expectOpenProposalEvent(tt, party, tc.proposal.ID)
   326  
   327  		eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true)
   328  		eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalOne(), true)
   329  		eng.ensureGetMarketFuture(t, marketID)
   330  		// when
   331  		_, err := eng.submitProposal(tt, tc.proposal)
   332  
   333  		// then
   334  		require.NoError(tt, err)
   335  	})
   336  }
   337  
   338  func testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails(t *testing.T) {
   339  	eng := getTestEngine(t, time.Now())
   340  
   341  	// given
   342  	party := vgrand.RandomStr(5)
   343  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   344  
   345  	tcs := []struct {
   346  		name     string
   347  		proposal types.Proposal
   348  	}{
   349  		{
   350  			name:     "For new market",
   351  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, false),
   352  		}, {
   353  			name:     "For new asset",
   354  			proposal: eng.newProposalForNewAsset(party, now),
   355  		}, {
   356  			name:     "Freeform",
   357  			proposal: eng.newFreeformProposal(party, now),
   358  		},
   359  	}
   360  
   361  	for _, tc := range tcs {
   362  		t.Run(tc.name, func(tt *testing.T) {
   363  			// setup
   364  			eng.ensureAllAssetEnabled(tt)
   365  			eng.ensureNoAccountForParty(tt, party)
   366  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens)
   367  
   368  			// when
   369  			_, err := eng.submitProposal(tt, tc.proposal)
   370  
   371  			// then
   372  			require.Error(tt, err)
   373  			assert.EqualError(tt, err, errNoBalanceForParty.Error())
   374  		})
   375  	}
   376  }
   377  
   378  func testSubmittingProposalWithoutEnoughStakeFails(t *testing.T) {
   379  	eng := getTestEngine(t, time.Now())
   380  
   381  	// given
   382  	party := vgrand.RandomStr(5)
   383  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   384  
   385  	tcs := []struct {
   386  		name                    string
   387  		minProposerBalanceParam string
   388  		proposal                types.Proposal
   389  	}{
   390  		{
   391  			name:                    "For new market",
   392  			minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance,
   393  			proposal:                eng.newProposalForNewMarket(party, now, nil, nil, true),
   394  		}, {
   395  			name:                    "For new asset",
   396  			minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance,
   397  			proposal:                eng.newProposalForNewAsset(party, now),
   398  		}, {
   399  			name:                    "Freeform",
   400  			minProposerBalanceParam: netparams.GovernanceProposalFreeformMinProposerBalance,
   401  			proposal:                eng.newFreeformProposal(party, now),
   402  		},
   403  	}
   404  
   405  	for _, tc := range tcs {
   406  		t.Run(tc.name, func(tt *testing.T) {
   407  			// setup
   408  			eng.ensureTokenBalanceForParty(tt, party, 10)
   409  			eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000")
   410  			eng.ensureAllAssetEnabled(tt)
   411  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens)
   412  
   413  			// when
   414  			_, err := eng.submitProposal(tt, tc.proposal)
   415  
   416  			// then
   417  			require.Error(tt, err)
   418  			assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=")
   419  		})
   420  
   421  		t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) {
   422  			// setup
   423  			eng.ensureAllAssetEnabled(tt)
   424  			eng.ensureNoAccountForParty(tt, party)
   425  
   426  			batchID := eng.newProposalID()
   427  
   428  			// expect
   429  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   430  			eng.expectProposalEvents(t, []expectedProposal{
   431  				{
   432  					partyID:    party,
   433  					proposalID: tc.proposal.ID,
   434  					state:      types.ProposalStateRejected,
   435  					reason:     types.ProposalErrorInsufficientTokens,
   436  				},
   437  			})
   438  
   439  			sub := eng.newBatchSubmission(
   440  				now.Add(48*time.Hour).Unix(),
   441  				tc.proposal,
   442  			)
   443  
   444  			// when
   445  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   446  
   447  			// then
   448  			require.Error(tt, err)
   449  			assert.EqualError(tt, err, errNoBalanceForParty.Error())
   450  		})
   451  	}
   452  }
   453  
   454  func testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails(t *testing.T) {
   455  	eng := getTestEngine(t, time.Now())
   456  
   457  	// given
   458  	party := vgrand.RandomStr(5)
   459  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   460  
   461  	tc := struct {
   462  		name                    string
   463  		minProposerBalanceParam string
   464  		proposal                types.Proposal
   465  	}{
   466  		name:                    "For market update",
   467  		minProposerBalanceParam: netparams.GovernanceProposalUpdateMarketMinProposerBalance,
   468  		proposal:                eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true),
   469  	}
   470  
   471  	t.Run(tc.name, func(tt *testing.T) {
   472  		// setup
   473  		eng.ensureTokenBalanceForParty(tt, party, 10)
   474  		eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000")
   475  		eng.ensureAllAssetEnabled(tt)
   476  		eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens)
   477  
   478  		// when
   479  		eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true)
   480  		eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalZero(), true)
   481  		_, err := eng.submitProposal(tt, tc.proposal)
   482  
   483  		// then
   484  		require.Error(tt, err)
   485  		assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=")
   486  	})
   487  
   488  	t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) {
   489  		// setup
   490  		eng.ensureTokenBalanceForParty(tt, party, 10)
   491  		eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000")
   492  		eng.ensureAllAssetEnabled(tt)
   493  
   494  		// when
   495  		eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true)
   496  		eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalZero(), true)
   497  		batchID := eng.newProposalID()
   498  
   499  		eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   500  		eng.expectProposalEvents(t, []expectedProposal{
   501  			{
   502  				partyID:    party,
   503  				proposalID: tc.proposal.ID,
   504  				state:      types.ProposalStateRejected,
   505  				reason:     types.ProposalErrorInsufficientTokens,
   506  			},
   507  		})
   508  
   509  		sub := eng.newBatchSubmission(
   510  			now.Add(48*time.Hour).Unix(),
   511  			tc.proposal,
   512  		)
   513  		_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   514  
   515  		// then
   516  		require.Error(tt, err)
   517  		assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=")
   518  	})
   519  }
   520  
   521  func testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails(t *testing.T) {
   522  	eng := getTestEngine(t, time.Now())
   523  
   524  	// given
   525  	party := vgrand.RandomStr(5)
   526  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   527  
   528  	tcs := []struct {
   529  		name                    string
   530  		minProposerBalanceParam string
   531  		proposal                types.Proposal
   532  	}{
   533  		{
   534  			name:                    "For new market",
   535  			minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance,
   536  			proposal:                eng.newProposalForNewMarket(party, now, nil, nil, false),
   537  		}, {
   538  			name:                    "For new asset",
   539  			minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance,
   540  			proposal:                eng.newProposalForNewAsset(party, now),
   541  		}, {
   542  			name:                    "Freeform",
   543  			minProposerBalanceParam: netparams.GovernanceProposalFreeformMinProposerBalance,
   544  			proposal:                eng.newFreeformProposal(party, now),
   545  		},
   546  	}
   547  
   548  	for _, tc := range tcs {
   549  		t.Run(tc.name, func(tt *testing.T) {
   550  			// setup
   551  			eng.ensureTokenBalanceForParty(tt, party, 10)
   552  			eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000")
   553  			eng.ensureAllAssetEnabled(tt)
   554  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens)
   555  
   556  			// when
   557  			_, err := eng.submitProposal(tt, tc.proposal)
   558  
   559  			// then
   560  			require.Error(tt, err)
   561  			assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=")
   562  		})
   563  
   564  		t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) {
   565  			// setup
   566  			eng.ensureTokenBalanceForParty(tt, party, 10)
   567  			eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000")
   568  			eng.ensureAllAssetEnabled(tt)
   569  
   570  			// when
   571  			batchID := eng.newProposalID()
   572  
   573  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   574  			eng.expectProposalEvents(t, []expectedProposal{
   575  				{
   576  					partyID:    party,
   577  					proposalID: tc.proposal.ID,
   578  					state:      types.ProposalStateRejected,
   579  					reason:     types.ProposalErrorInsufficientTokens,
   580  				},
   581  			})
   582  
   583  			sub := eng.newBatchSubmission(
   584  				now.Add(48*time.Hour).Unix(),
   585  				tc.proposal,
   586  			)
   587  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   588  
   589  			// then
   590  			require.Error(tt, err)
   591  			assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=")
   592  		})
   593  	}
   594  }
   595  
   596  func testSubmittingProposalWithClosingTimeTooSoonFails(t *testing.T) {
   597  	eng := getTestEngine(t, time.Now())
   598  
   599  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   600  	party := vgrand.RandomStr(5)
   601  
   602  	cases := []struct {
   603  		msg      string
   604  		proposal types.Proposal
   605  	}{
   606  		{
   607  			msg:      "For new market",
   608  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, true),
   609  		}, {
   610  			msg:      "For market update",
   611  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true),
   612  		}, {
   613  			msg:      "For new asset",
   614  			proposal: eng.newProposalForNewAsset(party, now),
   615  		},
   616  	}
   617  
   618  	for _, tc := range cases {
   619  		t.Run(tc.msg, func(tt *testing.T) {
   620  			// given
   621  			tc.proposal.Terms.ClosingTimestamp = now.Unix()
   622  
   623  			// setup
   624  			eng.ensureAllAssetEnabled(tt)
   625  
   626  			// expect
   627  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooSoon)
   628  
   629  			// when
   630  			_, err := eng.submitProposal(tt, tc.proposal)
   631  
   632  			// then
   633  			require.Error(tt, err)
   634  			assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >")
   635  		})
   636  
   637  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
   638  			// setup
   639  			eng.ensureAllAssetEnabled(tt)
   640  
   641  			batchID := eng.newProposalID()
   642  
   643  			// expect
   644  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   645  			eng.expectProposalEvents(t, []expectedProposal{
   646  				{
   647  					partyID:    party,
   648  					proposalID: tc.proposal.ID,
   649  					state:      types.ProposalStateRejected,
   650  					reason:     types.ProposalErrorCloseTimeTooSoon,
   651  				},
   652  			})
   653  
   654  			sub := eng.newBatchSubmission(
   655  				now.Unix(),
   656  				tc.proposal,
   657  			)
   658  
   659  			// when
   660  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   661  
   662  			// then
   663  			require.Error(tt, err)
   664  			assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >")
   665  		})
   666  	}
   667  }
   668  
   669  func testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooSoonFails(t *testing.T) {
   670  	eng := getTestEngine(t, time.Now())
   671  
   672  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   673  	party := vgrand.RandomStr(5)
   674  
   675  	cases := []struct {
   676  		msg      string
   677  		proposal types.Proposal
   678  	}{
   679  		{
   680  			msg:      "For new market",
   681  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, false),
   682  		}, {
   683  			msg:      "For market update",
   684  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, false),
   685  		}, {
   686  			msg:      "For new asset",
   687  			proposal: eng.newProposalForNewAsset(party, now),
   688  		},
   689  	}
   690  
   691  	for _, tc := range cases {
   692  		t.Run(tc.msg, func(tt *testing.T) {
   693  			// given
   694  			tc.proposal.Terms.ClosingTimestamp = now.Unix()
   695  
   696  			// setup
   697  			eng.ensureAllAssetEnabled(tt)
   698  
   699  			// expect
   700  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooSoon)
   701  
   702  			// when
   703  			_, err := eng.submitProposal(tt, tc.proposal)
   704  
   705  			// then
   706  			require.Error(tt, err)
   707  			assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >")
   708  		})
   709  
   710  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
   711  			// setup
   712  			eng.ensureAllAssetEnabled(tt)
   713  
   714  			batchID := eng.newProposalID()
   715  
   716  			// expect
   717  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   718  			eng.expectProposalEvents(t, []expectedProposal{
   719  				{
   720  					partyID:    party,
   721  					proposalID: tc.proposal.ID,
   722  					state:      types.ProposalStateRejected,
   723  					reason:     types.ProposalErrorCloseTimeTooSoon,
   724  				},
   725  			})
   726  
   727  			sub := eng.newBatchSubmission(
   728  				now.Unix(),
   729  				tc.proposal,
   730  			)
   731  
   732  			// when
   733  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   734  
   735  			// then
   736  			require.Error(tt, err)
   737  			assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >")
   738  		})
   739  	}
   740  }
   741  
   742  func testSubmittingProposalWithClosingTimeTooLateFails(t *testing.T) {
   743  	eng := getTestEngine(t, time.Now())
   744  
   745  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   746  	party := vgrand.RandomStr(5)
   747  
   748  	cases := []struct {
   749  		msg      string
   750  		proposal types.Proposal
   751  	}{
   752  		{
   753  			msg:      "For new market",
   754  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, true),
   755  		}, {
   756  			msg:      "For market update",
   757  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true),
   758  		}, {
   759  			msg:      "For new asset",
   760  			proposal: eng.newProposalForNewAsset(party, now),
   761  		},
   762  	}
   763  
   764  	for _, tc := range cases {
   765  		t.Run(tc.msg, func(tt *testing.T) {
   766  			// given
   767  			tc.proposal.Terms.ClosingTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix()
   768  
   769  			// setup
   770  			eng.ensureAllAssetEnabled(tt)
   771  
   772  			// expect
   773  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooLate)
   774  
   775  			// when
   776  			_, err := eng.submitProposal(tt, tc.proposal)
   777  
   778  			// then
   779  			require.Error(tt, err)
   780  			assert.Contains(tt, err.Error(), "proposal closing time too late, expected <")
   781  		})
   782  
   783  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
   784  			// setup
   785  			eng.ensureAllAssetEnabled(tt)
   786  
   787  			batchID := eng.newProposalID()
   788  
   789  			// expect
   790  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   791  			eng.expectProposalEvents(t, []expectedProposal{
   792  				{
   793  					partyID:    party,
   794  					proposalID: tc.proposal.ID,
   795  					state:      types.ProposalStateRejected,
   796  					reason:     types.ProposalErrorCloseTimeTooLate,
   797  				},
   798  			})
   799  
   800  			sub := eng.newBatchSubmission(
   801  				now.Add(3*365*24*time.Hour).Unix(),
   802  				tc.proposal,
   803  			)
   804  
   805  			// when
   806  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   807  
   808  			// then
   809  			require.Error(tt, err)
   810  			assert.Contains(tt, err.Error(), "proposal closing time too late, expected <")
   811  		})
   812  	}
   813  }
   814  
   815  func testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooLateFails(t *testing.T) {
   816  	eng := getTestEngine(t, time.Now())
   817  
   818  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   819  	party := vgrand.RandomStr(5)
   820  
   821  	cases := []struct {
   822  		msg      string
   823  		proposal types.Proposal
   824  	}{
   825  		{
   826  			msg:      "For new market",
   827  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, false),
   828  		}, {
   829  			msg:      "For market update",
   830  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, false),
   831  		}, {
   832  			msg:      "For new asset",
   833  			proposal: eng.newProposalForNewAsset(party, now),
   834  		},
   835  	}
   836  
   837  	for _, tc := range cases {
   838  		t.Run(tc.msg, func(tt *testing.T) {
   839  			// given
   840  			tc.proposal.Terms.ClosingTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix()
   841  
   842  			// setup
   843  			eng.ensureAllAssetEnabled(tt)
   844  
   845  			// expect
   846  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooLate)
   847  
   848  			// when
   849  			_, err := eng.submitProposal(tt, tc.proposal)
   850  
   851  			// then
   852  			require.Error(tt, err)
   853  			assert.Contains(tt, err.Error(), "proposal closing time too late, expected <")
   854  		})
   855  
   856  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
   857  			// setup
   858  			eng.ensureAllAssetEnabled(tt)
   859  
   860  			batchID := eng.newProposalID()
   861  
   862  			// expect
   863  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   864  			eng.expectProposalEvents(t, []expectedProposal{
   865  				{
   866  					partyID:    party,
   867  					proposalID: tc.proposal.ID,
   868  					state:      types.ProposalStateRejected,
   869  					reason:     types.ProposalErrorCloseTimeTooLate,
   870  				},
   871  			})
   872  
   873  			sub := eng.newBatchSubmission(
   874  				now.Add(3*365*24*time.Hour).Unix(),
   875  				tc.proposal,
   876  			)
   877  
   878  			// when
   879  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   880  
   881  			// then
   882  			require.Error(tt, err)
   883  			assert.Contains(tt, err.Error(), "proposal closing time too late, expected <")
   884  		})
   885  	}
   886  }
   887  
   888  func testSubmittingTimeTriggeredProposalNewMarketTerminationBeforeEnactmentFails(t *testing.T) {
   889  	eng := getTestEngine(t, time.Now())
   890  
   891  	proposer := vgrand.RandomStr(5)
   892  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   893  
   894  	// Make a proporsal with termination time before enactment time
   895  	// Enactment time for new market is now + 96 hours
   896  	termTimeBeforeEnact := now.Add(1 * 48 * time.Hour).Add(47 * time.Hour).Add(15 * time.Minute)
   897  
   898  	filter, binding := produceTimeTriggeredDataSourceSpec(termTimeBeforeEnact)
   899  	proposal1 := eng.newProposalForNewMarket(proposer, now, filter, binding, true)
   900  
   901  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   902  	eng.ensureAllAssetEnabled(t)
   903  
   904  	eng.expectRejectedProposalEvent(t, proposer, proposal1.ID, types.ProposalErrorInvalidFutureProduct)
   905  	_, err := eng.submitProposal(t, proposal1)
   906  	require.Error(t, err)
   907  	assert.Contains(t, err.Error(), "data source spec termination time before enactment")
   908  
   909  	// Make another proposal with termination time same as enactment time
   910  	// Enactment time for new market is now + 96 hours
   911  	termTimeEqualEnact := now.Add(2 * 48 * time.Hour)
   912  	filter, binding = produceTimeTriggeredDataSourceSpec(termTimeEqualEnact)
   913  	proposal2 := eng.newProposalForNewMarket(proposer, now, filter, binding, true)
   914  
   915  	eng.ensureTokenBalanceForParty(t, proposer, 1)
   916  	eng.ensureAllAssetEnabled(t)
   917  	eng.expectRejectedProposalEvent(t, proposer, proposal2.ID, types.ProposalErrorInvalidFutureProduct)
   918  
   919  	_, err = eng.submitProposal(t, proposal2)
   920  	require.Error(t, err)
   921  	assert.Contains(t, err.Error(), "data source spec termination time before enactment")
   922  }
   923  
   924  func testSubmittingProposalWithEnactmentTimeTooSoonFails(t *testing.T) {
   925  	eng := getTestEngine(t, time.Now())
   926  
   927  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
   928  	party := vgrand.RandomStr(5)
   929  
   930  	cases := []struct {
   931  		msg      string
   932  		proposal types.Proposal
   933  	}{
   934  		{
   935  			msg:      "For new market",
   936  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, true),
   937  		}, {
   938  			msg:      "For market update",
   939  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true),
   940  		}, {
   941  			msg:      "For new asset",
   942  			proposal: eng.newProposalForNewAsset(party, now),
   943  		},
   944  	}
   945  
   946  	for _, tc := range cases {
   947  		t.Run(tc.msg, func(tt *testing.T) {
   948  			// given
   949  			tc.proposal.Terms.EnactmentTimestamp = now.Unix()
   950  
   951  			// setup
   952  			eng.ensureAllAssetEnabled(tt)
   953  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorEnactTimeTooSoon)
   954  
   955  			// when
   956  			_, err := eng.submitProposal(tt, tc.proposal)
   957  
   958  			// then
   959  			require.Error(tt, err)
   960  			assert.Contains(tt, err.Error(), "proposal enactment time too soon, expected >")
   961  		})
   962  
   963  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
   964  			// given
   965  			tc.proposal.Terms.EnactmentTimestamp = now.Unix()
   966  
   967  			// setup
   968  			eng.ensureAllAssetEnabled(tt)
   969  
   970  			batchID := eng.newProposalID()
   971  
   972  			// expect
   973  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
   974  			eng.expectProposalEvents(t, []expectedProposal{
   975  				{
   976  					partyID:    party,
   977  					proposalID: tc.proposal.ID,
   978  					state:      types.ProposalStateRejected,
   979  					reason:     types.ProposalErrorEnactTimeTooSoon,
   980  				},
   981  			})
   982  
   983  			sub := eng.newBatchSubmission(
   984  				now.Add(48*time.Hour).Unix(),
   985  				tc.proposal,
   986  			)
   987  
   988  			// when
   989  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
   990  
   991  			// then
   992  			require.Error(tt, err)
   993  			assert.Contains(tt, err.Error(), "proposal enactment time too soon, expected >")
   994  		})
   995  	}
   996  }
   997  
   998  func testSubmittingProposalWithEnactmentTimeTooLateFails(t *testing.T) {
   999  	eng := getTestEngine(t, time.Now())
  1000  
  1001  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
  1002  	party := vgrand.RandomStr(5)
  1003  
  1004  	cases := []struct {
  1005  		msg      string
  1006  		proposal types.Proposal
  1007  	}{
  1008  		{
  1009  			msg:      "For new market",
  1010  			proposal: eng.newProposalForNewMarket(party, now, nil, nil, true),
  1011  		}, {
  1012  			msg:      "For market update",
  1013  			proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true),
  1014  		}, {
  1015  			msg:      "For new asset",
  1016  			proposal: eng.newProposalForNewAsset(party, now),
  1017  		},
  1018  	}
  1019  
  1020  	for _, tc := range cases {
  1021  		t.Run(tc.msg, func(tt *testing.T) {
  1022  			// given
  1023  			tc.proposal.Terms.EnactmentTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix()
  1024  
  1025  			// setup
  1026  			eng.ensureAllAssetEnabled(tt)
  1027  
  1028  			// expect
  1029  			eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorEnactTimeTooLate)
  1030  
  1031  			// when
  1032  			_, err := eng.submitProposal(tt, tc.proposal)
  1033  
  1034  			// then
  1035  			require.Error(tt, err)
  1036  			assert.Contains(tt, err.Error(), "proposal enactment time too late, expected <")
  1037  		})
  1038  
  1039  		t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) {
  1040  			// given
  1041  			tc.proposal.Terms.EnactmentTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix()
  1042  
  1043  			// setup
  1044  			eng.ensureAllAssetEnabled(tt)
  1045  
  1046  			batchID := eng.newProposalID()
  1047  
  1048  			// expect
  1049  			eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected)
  1050  			eng.expectProposalEvents(t, []expectedProposal{
  1051  				{
  1052  					partyID:    party,
  1053  					proposalID: tc.proposal.ID,
  1054  					state:      types.ProposalStateRejected,
  1055  					reason:     types.ProposalErrorEnactTimeTooLate,
  1056  				},
  1057  			})
  1058  
  1059  			sub := eng.newBatchSubmission(
  1060  				now.Add(48*time.Hour).Unix(),
  1061  				tc.proposal,
  1062  			)
  1063  
  1064  			// when
  1065  			_, err := eng.submitBatchProposal(tt, sub, batchID, party)
  1066  
  1067  			// then
  1068  			require.Error(tt, err)
  1069  			assert.Contains(tt, err.Error(), "proposal enactment time too late, expected <")
  1070  		})
  1071  	}
  1072  }
  1073  
  1074  func testVotingOnNonExistingProposalFails(t *testing.T) {
  1075  	eng := getTestEngine(t, time.Now())
  1076  
  1077  	// when
  1078  	voter := vgrand.RandomStr(5)
  1079  
  1080  	// setup
  1081  	eng.ensureAllAssetEnabled(t)
  1082  
  1083  	// when
  1084  	err := eng.addYesVote(t, voter, vgrand.RandomStr(5))
  1085  
  1086  	// then
  1087  	require.Error(t, err)
  1088  	assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error())
  1089  }
  1090  
  1091  func testVotingWithNonExistingAccountFails(t *testing.T) {
  1092  	eng := getTestEngine(t, time.Now())
  1093  
  1094  	// given
  1095  	proposer := vgrand.RandomStr(5)
  1096  	proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
  1097  
  1098  	// setup
  1099  	eng.ensureAllAssetEnabled(t)
  1100  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1101  
  1102  	// expect
  1103  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1104  
  1105  	// when
  1106  	_, err := eng.submitProposal(t, proposal)
  1107  
  1108  	// then
  1109  	require.NoError(t, err)
  1110  
  1111  	// given
  1112  	voterWithoutAccount := "voter-no-account"
  1113  
  1114  	// setup
  1115  	eng.ensureNoAccountForParty(t, voterWithoutAccount)
  1116  
  1117  	// when
  1118  	err = eng.addYesVote(t, voterWithoutAccount, proposal.ID)
  1119  
  1120  	// then
  1121  	require.Error(t, err)
  1122  	assert.EqualError(t, err, errNoBalanceForParty.Error())
  1123  }
  1124  
  1125  func testVotingWithoutTokenFails(t *testing.T) {
  1126  	eng := getTestEngine(t, time.Now())
  1127  
  1128  	// given
  1129  	proposer := eng.newValidParty("proposer", 1)
  1130  	proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
  1131  
  1132  	// setup
  1133  	eng.ensureAllAssetEnabled(t)
  1134  	eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID)
  1135  
  1136  	// when
  1137  	_, err := eng.submitProposal(t, proposal)
  1138  
  1139  	// then
  1140  	require.NoError(t, err)
  1141  
  1142  	// given
  1143  	voterWithEmptyAccount := vgrand.RandomStr(5)
  1144  
  1145  	// setup
  1146  	eng.ensureTokenBalanceForParty(t, voterWithEmptyAccount, 0)
  1147  
  1148  	// when
  1149  	err = eng.addYesVote(t, voterWithEmptyAccount, proposal.ID)
  1150  
  1151  	// then
  1152  	require.Error(t, err)
  1153  	assert.EqualError(t, err, governance.ErrVoterInsufficientTokens.Error())
  1154  }
  1155  
  1156  func testMultipleProposalsLifecycle(t *testing.T) {
  1157  	eng := getTestEngine(t, time.Now())
  1158  
  1159  	// given
  1160  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
  1161  	partyA := vgrand.RandomStr(5)
  1162  	partyB := vgrand.RandomStr(5)
  1163  
  1164  	// setup
  1165  	eng.ensureAllAssetEnabled(t)
  1166  	eng.accounts.EXPECT().GetStakingAssetTotalSupply().AnyTimes().Return(num.NewUint(300))
  1167  	eng.tokenBal[partyA] = 200
  1168  	eng.tokenBal[partyB] = 100
  1169  
  1170  	const howMany = 10
  1171  	passed := map[string]*types.Proposal{}
  1172  	declined := map[string]*types.Proposal{}
  1173  	var afterClosing time.Time
  1174  	var afterEnactment time.Time
  1175  
  1176  	for i := 0; i < howMany; i++ {
  1177  		toBePassed := eng.newProposalForNewMarket(partyA, now, nil, nil, true)
  1178  		eng.expectOpenProposalEvent(t, partyA, toBePassed.ID)
  1179  		_, err := eng.submitProposal(t, toBePassed)
  1180  		require.NoError(t, err)
  1181  		passed[toBePassed.ID] = &toBePassed
  1182  
  1183  		toBeDeclined := eng.newProposalForNewMarket(partyB, now, nil, nil, true)
  1184  		eng.expectOpenProposalEvent(t, partyB, toBeDeclined.ID)
  1185  		_, err = eng.submitProposal(t, toBeDeclined)
  1186  		require.NoError(t, err)
  1187  		declined[toBeDeclined.ID] = &toBeDeclined
  1188  
  1189  		if i == 0 {
  1190  			// all proposal terms are expected to be equal
  1191  			afterClosing = time.Unix(toBePassed.Terms.ClosingTimestamp, 0).Add(time.Second)
  1192  			afterEnactment = time.Unix(toBePassed.Terms.EnactmentTimestamp, 0).Add(time.Second)
  1193  		}
  1194  	}
  1195  	require.Len(t, passed, howMany)
  1196  	require.Len(t, declined, howMany)
  1197  
  1198  	for id := range passed {
  1199  		eng.expectVoteEvent(t, partyA, id)
  1200  		err := eng.addYesVote(t, partyA, id)
  1201  		require.NoError(t, err)
  1202  
  1203  		eng.expectVoteEvent(t, partyB, id)
  1204  		err = eng.addNoVote(t, partyB, id)
  1205  		require.NoError(t, err)
  1206  	}
  1207  
  1208  	for id := range declined {
  1209  		eng.expectVoteEvent(t, partyA, id)
  1210  		err := eng.addNoVote(t, partyA, id)
  1211  		require.NoError(t, err)
  1212  
  1213  		eng.expectVoteEvent(t, partyB, id)
  1214  		err = eng.addYesVote(t, partyB, id)
  1215  		require.NoError(t, err)
  1216  	}
  1217  
  1218  	var howManyPassed, howManyDeclined int
  1219  	eng.markets.EXPECT().GetMarketState(gomock.Any()).AnyTimes()
  1220  	eng.broker.EXPECT().Send(gomock.Any()).Times(howMany * 2).Do(func(evt events.Event) {
  1221  		pe, ok := evt.(*events.Proposal)
  1222  		require.True(t, ok)
  1223  		p := pe.Proposal()
  1224  		if p.State == types.ProposalStatePassed {
  1225  			_, found := passed[p.Id]
  1226  			assert.True(t, found, "passed proposal is in the passed collection")
  1227  			howManyPassed++
  1228  		} else if p.State == types.ProposalStateDeclined {
  1229  			_, found := declined[p.Id]
  1230  			assert.True(t, found, "declined proposal is in the declined collection")
  1231  			howManyDeclined++
  1232  		} else {
  1233  			assert.FailNow(t, "unexpected proposal state")
  1234  		}
  1235  	})
  1236  	eng.broker.EXPECT().SendBatch(gomock.Any()).Times(howMany * 2)
  1237  	eng.OnTick(context.Background(), afterClosing)
  1238  	assert.Equal(t, howMany, howManyPassed)
  1239  	assert.Equal(t, howMany, howManyDeclined)
  1240  
  1241  	toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment)
  1242  	require.Len(t, toBeEnacted, howMany)
  1243  	for i := 0; i < howMany; i++ {
  1244  		_, found := passed[toBeEnacted[i].Proposal().ID]
  1245  		assert.True(t, found)
  1246  	}
  1247  }
  1248  
  1249  func testWithdrawingVoteAssetRemovesVoteFromProposalStateCalculation(t *testing.T) {
  1250  	eng := getTestEngine(t, time.Now())
  1251  
  1252  	// given
  1253  	proposer := vgrand.RandomStr(5)
  1254  	proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true)
  1255  
  1256  	// setup
  1257  	eng.ensureAllAssetEnabled(t)
  1258  	eng.ensureStakingAssetTotalSupply(t, 200)
  1259  	eng.ensureTokenBalanceForParty(t, proposer, 100)
  1260  
  1261  	// expect
  1262  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1263  
  1264  	// when
  1265  	_, err := eng.submitProposal(t, proposal)
  1266  
  1267  	// then
  1268  	require.NoError(t, err)
  1269  
  1270  	// given
  1271  	voter := vgrand.RandomStr(5)
  1272  
  1273  	// setup
  1274  	eng.ensureTokenBalanceForParty(t, voter, 100)
  1275  
  1276  	// expect
  1277  	eng.expectVoteEvent(t, voter, proposal.ID)
  1278  
  1279  	// when
  1280  	err = eng.addYesVote(t, voter, proposal.ID)
  1281  
  1282  	// then
  1283  	require.NoError(t, err)
  1284  
  1285  	// setup
  1286  	eng.ensureTokenBalanceForParty(t, voter, 100)
  1287  
  1288  	// expect
  1289  	eng.expectVoteEvent(t, voter, proposal.ID)
  1290  
  1291  	// when
  1292  	err = eng.addNoVote(t, voter, proposal.ID)
  1293  
  1294  	// then
  1295  	require.NoError(t, err)
  1296  
  1297  	// given
  1298  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1299  
  1300  	// setup
  1301  	eng.ensureTokenBalanceForParty(t, voter, 0)
  1302  
  1303  	// expect
  1304  	eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached)
  1305  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "0", "0")
  1306  
  1307  	// when
  1308  	eng.expectGetMarketState(t, proposal.ID)
  1309  	_, voteClosed := eng.OnTick(context.Background(), afterClosing)
  1310  
  1311  	// then
  1312  	require.Len(t, voteClosed, 1)
  1313  	vc := voteClosed[0]
  1314  	require.NotNil(t, vc.NewMarket())
  1315  	assert.True(t, vc.NewMarket().Rejected())
  1316  
  1317  	// given
  1318  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
  1319  
  1320  	// when
  1321  	toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment)
  1322  
  1323  	// then
  1324  	assert.Empty(t, toBeEnacted)
  1325  }
  1326  
  1327  func testComputingGovernanceStateHashIsDeterministic(t *testing.T) {
  1328  	eng := getTestEngine(t, time.Now())
  1329  
  1330  	require.Equal(t,
  1331  		"a1292c11ccdb876535c6699e8217e1a1294190d83e4233ecc490d32df17a4116",
  1332  		hex.EncodeToString(eng.Hash()),
  1333  		"hash is not deterministic",
  1334  	)
  1335  
  1336  	// when
  1337  	proposer := vgrand.RandomStr(5)
  1338  	now := eng.tsvc.GetTimeNow().Add(2 * time.Hour)
  1339  	proposal := eng.newProposalForNewMarket(proposer, now, nil, nil, true)
  1340  
  1341  	// setup
  1342  	eng.ensureTokenBalanceForParty(t, proposer, 1)
  1343  	eng.ensureStakingAssetTotalSupply(t, 9)
  1344  	eng.ensureAllAssetEnabled(t)
  1345  
  1346  	// expect
  1347  	eng.expectOpenProposalEvent(t, proposer, proposal.ID)
  1348  
  1349  	// when
  1350  	_, err := eng.submitProposal(t, proposal)
  1351  
  1352  	// then
  1353  	require.NoError(t, err)
  1354  
  1355  	// given
  1356  	voter1 := vgrand.RandomStr(5)
  1357  
  1358  	// setup
  1359  	eng.ensureTokenBalanceForParty(t, voter1, 7)
  1360  
  1361  	// expect
  1362  	eng.expectVoteEvent(t, voter1, proposal.ID)
  1363  
  1364  	// then
  1365  	err = eng.addYesVote(t, voter1, proposal.ID)
  1366  
  1367  	// then
  1368  	require.NoError(t, err)
  1369  	// test hash before enactment
  1370  	require.Equal(t,
  1371  		"d43f721a8e28c5bad0e78ab7052b8990be753044bb355056519fab76e8de50a7",
  1372  		hex.EncodeToString(eng.Hash()),
  1373  		"hash is not deterministic",
  1374  	)
  1375  
  1376  	// given
  1377  	afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second)
  1378  
  1379  	// setup
  1380  	eng.ensureTokenBalanceForParty(t, voter1, 7)
  1381  
  1382  	// expect
  1383  	eng.expectPassedProposalEvent(t, proposal.ID)
  1384  	eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7")
  1385  
  1386  	// when
  1387  	eng.expectGetMarketState(t, proposal.ID)
  1388  	eng.OnTick(context.Background(), afterClosing)
  1389  
  1390  	// given
  1391  	afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second)
  1392  
  1393  	// when
  1394  	// no calculations, no state change, simply removed from governance engine
  1395  	eng.expectGetMarketState(t, proposal.ID)
  1396  	toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment)
  1397  
  1398  	// then
  1399  	require.Len(t, toBeEnacted, 1)
  1400  	require.Equal(t,
  1401  		"fbf86f159b135501153cda0fc333751df764290a3ae61c3f45f19f9c19445563",
  1402  		hex.EncodeToString(eng.Hash()),
  1403  		"hash is not deterministic",
  1404  	)
  1405  }
  1406  
  1407  func getTestEngine(t *testing.T, now time.Time) *tstEngine {
  1408  	t.Helper()
  1409  
  1410  	cfg := governance.NewDefaultConfig()
  1411  	log := logging.NewTestLogger()
  1412  
  1413  	ctrl := gomock.NewController(t)
  1414  	accounts := mocks.NewMockStakingAccounts(ctrl)
  1415  	markets := mocks.NewMockMarkets(ctrl)
  1416  	assets := mocks.NewMockAssets(ctrl)
  1417  	ts := mocks.NewMockTimeService(ctrl)
  1418  	broker := bmocks.NewMockBroker(ctrl)
  1419  	witness := mocks.NewMockWitness(ctrl)
  1420  	banking := mocks.NewMockBanking(ctrl)
  1421  
  1422  	// Set default network parameters
  1423  	netp := netparams.New(log, netparams.NewDefaultConfig(), broker)
  1424  
  1425  	ctx := context.Background()
  1426  
  1427  	ts.EXPECT().GetTimeNow().Return(now).AnyTimes()
  1428  
  1429  	broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalMarketMinVoterBalance, "1")).Times(1)
  1430  	require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalMarketMinVoterBalance, "1"))
  1431  
  1432  	broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalMarketRequiredParticipation, "0.5")).Times(1)
  1433  	require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalMarketRequiredParticipation, "0.5"))
  1434  
  1435  	broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1")).Times(1)
  1436  	require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1"))
  1437  
  1438  	// Initialise engine as validator
  1439  	eng := governance.NewEngine(log, cfg, accounts, ts, broker, assets, witness, markets, netp, banking)
  1440  	require.NotNil(t, eng)
  1441  
  1442  	tEng := &tstEngine{
  1443  		Engine:   eng,
  1444  		ctrl:     ctrl,
  1445  		accounts: accounts,
  1446  		markets:  markets,
  1447  		tsvc:     ts,
  1448  		broker:   broker,
  1449  		assets:   assets,
  1450  		witness:  witness,
  1451  		netp:     netp,
  1452  		tokenBal: map[string]uint64{},
  1453  		els:      map[string]map[string]float64{},
  1454  	}
  1455  	// ensure the balance is always returned as expected
  1456  
  1457  	broker.EXPECT().Send(gomock.Any()).Times(1)
  1458  	tEng.netp.Update(context.Background(), netparams.BlockchainsPrimaryEthereumConfig, "{\"network_id\":\"1\",\"chain_id\":\"1\",\"collateral_bridge_contract\":{\"address\":\"0x23872549cE10B40e31D6577e0A920088B0E0666a\"},\"confirmations\":64,\"staking_bridge_contract\":{\"address\":\"0x195064D33f09e0c42cF98E665D9506e0dC17de68\",\"deployment_block_height\":13146644},\"token_vesting_contract\":{\"address\":\"0x23d1bFE8fA50a167816fBD79D7932577c06011f4\",\"deployment_block_height\":12834524},\"multisig_control_contract\":{\"address\":\"0xDD2df0E7583ff2acfed5e49Df4a424129cA9B58F\",\"deployment_block_height\":15263593}}")
  1459  	tEng.accounts.EXPECT().GetAvailableBalance(gomock.Any()).AnyTimes().DoAndReturn(func(p string) (*num.Uint, error) {
  1460  		b, ok := tEng.tokenBal[p]
  1461  		if !ok {
  1462  			return nil, errNoBalanceForParty
  1463  		}
  1464  		return num.NewUint(b), nil
  1465  	})
  1466  	return tEng
  1467  }
  1468  
  1469  func newFreeformTerms() *types.ProposalTermsNewFreeform {
  1470  	return &types.ProposalTermsNewFreeform{
  1471  		NewFreeform: &types.NewFreeform{},
  1472  	}
  1473  }
  1474  
  1475  func newAssetTerms() *types.ProposalTermsNewAsset {
  1476  	return &types.ProposalTermsNewAsset{
  1477  		NewAsset: &types.NewAsset{
  1478  			Changes: &types.AssetDetails{
  1479  				Name:     "token",
  1480  				Symbol:   "TKN",
  1481  				Decimals: 18,
  1482  				Quantum:  num.DecimalFromFloat(1),
  1483  				Source: &types.AssetDetailsBuiltinAsset{
  1484  					BuiltinAsset: &types.BuiltinAsset{
  1485  						MaxFaucetAmountMint: num.NewUint(1),
  1486  					},
  1487  				},
  1488  			},
  1489  		},
  1490  	}
  1491  }
  1492  
  1493  func produceNonTimeTriggeredDataSourceSpec() (*dstypes.SpecFilter, *datasource.SpecBindingForFuture) {
  1494  	return &dstypes.SpecFilter{
  1495  			Key: &dstypes.SpecPropertyKey{
  1496  				Name: "trading.terminated",
  1497  				Type: datapb.PropertyKey_TYPE_BOOLEAN,
  1498  			},
  1499  			Conditions: []*dstypes.SpecCondition{},
  1500  		},
  1501  		&datasource.SpecBindingForFuture{
  1502  			SettlementDataProperty:     "prices.ETH.value",
  1503  			TradingTerminationProperty: "trading.terminated",
  1504  		}
  1505  }
  1506  
  1507  func produceTimeTriggeredDataSourceSpec(termTimestamp time.Time) (*dstypes.SpecFilter, *datasource.SpecBindingForFuture) {
  1508  	return &dstypes.SpecFilter{
  1509  			Key: &dstypes.SpecPropertyKey{
  1510  				Name: "vegaprotocol.builtin.timestamp",
  1511  				Type: datapb.PropertyKey_TYPE_TIMESTAMP,
  1512  			},
  1513  			Conditions: []*dstypes.SpecCondition{
  1514  				{
  1515  					Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1516  					Value:    strconv.FormatInt(termTimestamp.Unix(), 10),
  1517  				},
  1518  			},
  1519  		},
  1520  		&datasource.SpecBindingForFuture{
  1521  			SettlementDataProperty:     "prices.ETH.value",
  1522  			TradingTerminationProperty: "vegaprotocol.builtin.timestamp",
  1523  		}
  1524  }
  1525  
  1526  func newNetParamTerms(key, value string) *types.ProposalTermsUpdateNetworkParameter {
  1527  	return &types.ProposalTermsUpdateNetworkParameter{
  1528  		UpdateNetworkParameter: &types.UpdateNetworkParameter{
  1529  			Changes: &types.NetworkParameter{
  1530  				Key:   key,
  1531  				Value: value,
  1532  			},
  1533  		},
  1534  	}
  1535  }
  1536  
  1537  func newMarketTerms(termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool, successor *types.SuccessorConfig, fCap *types.FutureCap) *types.ProposalTermsNewMarket {
  1538  	var dt *dsdefinition.Definition
  1539  	if termExt {
  1540  		if termFilter == nil {
  1541  			termFilter, termBinding = produceNonTimeTriggeredDataSourceSpec()
  1542  		}
  1543  
  1544  		dt = datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig(
  1545  			&signedoracle.SpecConfiguration{
  1546  				Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
  1547  				Filters: []*dstypes.SpecFilter{
  1548  					termFilter,
  1549  				},
  1550  			},
  1551  		)
  1552  	} else {
  1553  		tm := time.Now().Add(time.Hour * 24 * 365)
  1554  		if termFilter == nil {
  1555  			_, termBinding = produceTimeTriggeredDataSourceSpec(tm)
  1556  		}
  1557  
  1558  		dt = datasource.NewDefinition(datasource.ContentTypeInternalTimeTermination).SetTimeTriggerConditionConfig(
  1559  			[]*dstypes.SpecCondition{
  1560  				{
  1561  					Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1562  					Value:    fmt.Sprintf("%d", tm.UnixNano()),
  1563  				},
  1564  			},
  1565  		)
  1566  	}
  1567  
  1568  	return &types.ProposalTermsNewMarket{
  1569  		NewMarket: &types.NewMarket{
  1570  			Changes: &types.NewMarketConfiguration{
  1571  				Instrument: &types.InstrumentConfiguration{
  1572  					Name: "June 2020 GBP vs VUSD future",
  1573  					Code: "CRYPTO:GBPVUSD/JUN20",
  1574  					Product: &types.InstrumentConfigurationFuture{
  1575  						Future: &types.FutureProduct{
  1576  							SettlementAsset: "VUSD",
  1577  							QuoteName:       "VUSD",
  1578  							DataSourceSpecForSettlementData: *datasource.NewDefinition(
  1579  								datasource.ContentTypeOracle,
  1580  							).SetOracleConfig(
  1581  								&signedoracle.SpecConfiguration{
  1582  									Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
  1583  									Filters: []*dstypes.SpecFilter{
  1584  										{
  1585  											Key: &dstypes.SpecPropertyKey{
  1586  												Name: "prices.ETH.value",
  1587  												Type: datapb.PropertyKey_TYPE_INTEGER,
  1588  											},
  1589  											Conditions: []*dstypes.SpecCondition{
  1590  												{
  1591  													Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1592  													Value:    "0",
  1593  												},
  1594  											},
  1595  										},
  1596  									},
  1597  								},
  1598  							),
  1599  							DataSourceSpecForTradingTermination: *dt,
  1600  							DataSourceSpecBinding:               termBinding,
  1601  							Cap:                                 fCap,
  1602  						},
  1603  					},
  1604  				},
  1605  				RiskParameters: &types.NewMarketConfigurationLogNormal{
  1606  					LogNormal: &types.LogNormalRiskModel{
  1607  						RiskAversionParameter: num.DecimalFromFloat(0.01),
  1608  						Tau:                   num.DecimalFromFloat(0.00011407711613050422),
  1609  						Params: &types.LogNormalModelParams{
  1610  							Mu:    num.DecimalZero(),
  1611  							R:     num.DecimalFromFloat(0.016),
  1612  							Sigma: num.DecimalFromFloat(0.09),
  1613  						},
  1614  					},
  1615  				},
  1616  				Metadata:      []string{"asset_class:fx/crypto", "product:futures"},
  1617  				DecimalPlaces: 0,
  1618  				LiquiditySLAParameters: &types.LiquiditySLAParams{
  1619  					PriceRange:                  num.DecimalFromFloat(0.95),
  1620  					CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
  1621  					PerformanceHysteresisEpochs: 4,
  1622  					SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
  1623  				},
  1624  				LinearSlippageFactor:    num.DecimalFromFloat(0.1),
  1625  				QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
  1626  				Successor:               successor,
  1627  				LiquidationStrategy: &types.LiquidationStrategy{
  1628  					DisposalTimeStep:    10 * time.Second,
  1629  					DisposalFraction:    num.DecimalFromFloat(0.1),
  1630  					FullDisposalSize:    20,
  1631  					MaxFractionConsumed: num.DecimalFromFloat(0.01),
  1632  					DisposalSlippage:    num.DecimalFromFloat(0.1),
  1633  				},
  1634  				TickSize: num.NewUint(1),
  1635  			},
  1636  		},
  1637  	}
  1638  }
  1639  
  1640  //nolint:unparam
  1641  func newPerpsMarketTerms(termFilter *dstypes.SpecFilter, binding *datasource.SpecBindingForPerps) *types.ProposalTermsNewMarket {
  1642  	if binding == nil {
  1643  		binding = &datasource.SpecBindingForPerps{
  1644  			SettlementDataProperty:     "price.ETH.value",
  1645  			SettlementScheduleProperty: "vegaprotocol.builtin.timetrigger",
  1646  		}
  1647  	}
  1648  
  1649  	return &types.ProposalTermsNewMarket{
  1650  		NewMarket: &types.NewMarket{
  1651  			Changes: &types.NewMarketConfiguration{
  1652  				Instrument: &types.InstrumentConfiguration{
  1653  					Name: "GBP/USDT PERPS",
  1654  					Code: "CRYPTO:GBP/USD",
  1655  					Product: &types.InstrumentConfigurationPerps{
  1656  						Perps: &types.PerpsProduct{
  1657  							SettlementAsset: "USDT",
  1658  							QuoteName:       "USD",
  1659  							DataSourceSpecForSettlementData: *datasource.NewDefinition(
  1660  								datasource.ContentTypeOracle,
  1661  							).SetOracleConfig(
  1662  								&signedoracle.SpecConfiguration{
  1663  									Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
  1664  									Filters: []*dstypes.SpecFilter{
  1665  										{
  1666  											Key: &dstypes.SpecPropertyKey{
  1667  												Name: "price.ETH.value",
  1668  												Type: datapb.PropertyKey_TYPE_INTEGER,
  1669  											},
  1670  											Conditions: []*dstypes.SpecCondition{
  1671  												{
  1672  													Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1673  													Value:    "0",
  1674  												},
  1675  											},
  1676  										},
  1677  									},
  1678  								},
  1679  							),
  1680  							DataSourceSpecForSettlementSchedule: *datasource.NewDefinition(datasource.ContentTypeInternalTimeTriggerTermination).SetTimeTriggerTriggersConfig(
  1681  								common.InternalTimeTriggers{
  1682  									{
  1683  										Initial: nil,
  1684  										Every:   300,
  1685  									},
  1686  								},
  1687  							).SetTimeTriggerConditionConfig([]*dstypes.SpecCondition{
  1688  								{
  1689  									Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1690  									Value:    "0",
  1691  								},
  1692  							}),
  1693  							DataSourceSpecBinding: binding,
  1694  						},
  1695  					},
  1696  				},
  1697  				RiskParameters: &types.NewMarketConfigurationLogNormal{
  1698  					LogNormal: &types.LogNormalRiskModel{
  1699  						RiskAversionParameter: num.DecimalFromFloat(0.01),
  1700  						Tau:                   num.DecimalFromFloat(0.00011407711613050422),
  1701  						Params: &types.LogNormalModelParams{
  1702  							Mu:    num.DecimalZero(),
  1703  							R:     num.DecimalFromFloat(0.016),
  1704  							Sigma: num.DecimalFromFloat(0.09),
  1705  						},
  1706  					},
  1707  				},
  1708  				Metadata:      []string{"asset_class:fx/crypto", "product:futures"},
  1709  				DecimalPlaces: 0,
  1710  				LiquiditySLAParameters: &types.LiquiditySLAParams{
  1711  					PriceRange:                  num.DecimalFromFloat(0.95),
  1712  					CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
  1713  					PerformanceHysteresisEpochs: 4,
  1714  					SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
  1715  				},
  1716  				LinearSlippageFactor:    num.DecimalFromFloat(0.1),
  1717  				QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
  1718  				LiquidationStrategy: &types.LiquidationStrategy{
  1719  					DisposalTimeStep:    10 * time.Second,
  1720  					DisposalFraction:    num.DecimalFromFloat(0.1),
  1721  					FullDisposalSize:    20,
  1722  					MaxFractionConsumed: num.DecimalFromFloat(0.01),
  1723  					DisposalSlippage:    num.DecimalFromFloat(0.1),
  1724  				},
  1725  				TickSize: num.UintOne(),
  1726  			},
  1727  		},
  1728  	}
  1729  }
  1730  
  1731  func newUpdateMarketState(tp types.MarketStateUpdateType, marketID string, price *num.Uint) *types.ProposalTermsUpdateMarketState {
  1732  	return &types.ProposalTermsUpdateMarketState{
  1733  		UpdateMarketState: &types.UpdateMarketState{
  1734  			Changes: &types.MarketStateUpdateConfiguration{
  1735  				MarketID:        marketID,
  1736  				SettlementPrice: price,
  1737  				UpdateType:      tp,
  1738  			},
  1739  		},
  1740  	}
  1741  }
  1742  
  1743  func newSpotMarketTerms() *types.ProposalTermsNewSpotMarket {
  1744  	return &types.ProposalTermsNewSpotMarket{
  1745  		NewSpotMarket: &types.NewSpotMarket{
  1746  			Changes: &types.NewSpotMarketConfiguration{
  1747  				Instrument: &types.InstrumentConfiguration{
  1748  					Name: "BTC/USDT Spot",
  1749  					Code: "CRYPTO:BTCUSDT",
  1750  					Product: &types.InstrumentConfigurationSpot{
  1751  						Spot: &types.SpotProduct{
  1752  							Name:       "BTC/USDT",
  1753  							BaseAsset:  "BTC",
  1754  							QuoteAsset: "USDT",
  1755  						},
  1756  					},
  1757  				},
  1758  				RiskParameters: &types.NewSpotMarketConfigurationLogNormal{
  1759  					LogNormal: &types.LogNormalRiskModel{
  1760  						RiskAversionParameter: num.DecimalFromFloat(0.01),
  1761  						Tau:                   num.DecimalFromFloat(0.00011407711613050422),
  1762  						Params: &types.LogNormalModelParams{
  1763  							Mu:    num.DecimalZero(),
  1764  							R:     num.DecimalFromFloat(0.016),
  1765  							Sigma: num.DecimalFromFloat(0.09),
  1766  						},
  1767  					},
  1768  				},
  1769  				Metadata:           []string{"asset_class:spot/crypto", "product:spot"},
  1770  				PriceDecimalPlaces: 0,
  1771  				SLAParams: &types.LiquiditySLAParams{
  1772  					PriceRange:                  num.DecimalOne(),
  1773  					CommitmentMinTimeFraction:   num.DecimalFromFloat(0.5),
  1774  					SlaCompetitionFactor:        num.DecimalOne(),
  1775  					PerformanceHysteresisEpochs: 1,
  1776  				},
  1777  				TickSize: num.UintOne(),
  1778  			},
  1779  		},
  1780  	}
  1781  }
  1782  
  1783  func updateSpotMarketTerms() *types.ProposalTermsUpdateSpotMarket {
  1784  	return &types.ProposalTermsUpdateSpotMarket{
  1785  		UpdateSpotMarket: &types.UpdateSpotMarket{
  1786  			MarketID: vgrand.RandomStr(5),
  1787  			Changes: &types.UpdateSpotMarketConfiguration{
  1788  				Instrument: &types.InstrumentConfiguration{
  1789  					Name: "some name",
  1790  					Code: "some code",
  1791  				},
  1792  				RiskParameters: &types.UpdateSpotMarketConfigurationLogNormal{
  1793  					LogNormal: &types.LogNormalRiskModel{
  1794  						RiskAversionParameter: num.DecimalFromFloat(0.02),
  1795  						Tau:                   num.DecimalFromFloat(0.0002),
  1796  						Params: &types.LogNormalModelParams{
  1797  							Mu:    num.DecimalZero(),
  1798  							R:     num.DecimalFromFloat(0.015),
  1799  							Sigma: num.DecimalFromFloat(0.08),
  1800  						},
  1801  					},
  1802  				},
  1803  				Metadata: []string{"asset_class:spot/crypto", "product:spot"},
  1804  				SLAParams: &types.LiquiditySLAParams{
  1805  					PriceRange:                  num.DecimalOne(),
  1806  					CommitmentMinTimeFraction:   num.DecimalFromFloat(0.2),
  1807  					SlaCompetitionFactor:        num.DecimalFromFloat(0.23),
  1808  					PerformanceHysteresisEpochs: 2,
  1809  				},
  1810  				TargetStakeParameters: &types.TargetStakeParameters{
  1811  					TimeWindow:    1,
  1812  					ScalingFactor: num.DecimalE(),
  1813  				},
  1814  				TickSize: num.UintOne(),
  1815  			},
  1816  		},
  1817  	}
  1818  }
  1819  
  1820  func updateMarketTerms(termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool) *types.ProposalTermsUpdateMarket {
  1821  	var dt *dsdefinition.Definition
  1822  	if termExt {
  1823  		if termFilter == nil {
  1824  			termFilter = &dstypes.SpecFilter{
  1825  				Key: &dstypes.SpecPropertyKey{
  1826  					Name: "trading.terminated",
  1827  					Type: datapb.PropertyKey_TYPE_BOOLEAN,
  1828  				},
  1829  				Conditions: []*dstypes.SpecCondition{},
  1830  			}
  1831  
  1832  			termBinding = &datasource.SpecBindingForFuture{
  1833  				SettlementDataProperty:     "prices.ETH.value",
  1834  				TradingTerminationProperty: "trading.terminated",
  1835  			}
  1836  		}
  1837  		dt = datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig(
  1838  			&signedoracle.SpecConfiguration{
  1839  				Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
  1840  				Filters: []*dstypes.SpecFilter{
  1841  					termFilter,
  1842  				},
  1843  			},
  1844  		)
  1845  	} else {
  1846  		tm := time.Now().Add(time.Hour * 24 * 365)
  1847  		if termFilter == nil {
  1848  			_, termBinding = produceTimeTriggeredDataSourceSpec(tm)
  1849  		}
  1850  
  1851  		dt = datasource.NewDefinition(datasource.ContentTypeInternalTimeTermination).SetTimeTriggerConditionConfig(
  1852  			[]*dstypes.SpecCondition{
  1853  				{
  1854  					Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1855  					Value:    fmt.Sprintf("%d", tm.UnixNano()),
  1856  				},
  1857  			},
  1858  		)
  1859  	}
  1860  
  1861  	return &types.ProposalTermsUpdateMarket{
  1862  		UpdateMarket: &types.UpdateMarket{
  1863  			MarketID: vgrand.RandomStr(5),
  1864  			Changes: &types.UpdateMarketConfiguration{
  1865  				Instrument: &types.UpdateInstrumentConfiguration{
  1866  					Code: "CRYPTO:GBPVUSD/JUN20",
  1867  					Name: "UPDATED_MARKET_NAME",
  1868  					Product: &types.UpdateInstrumentConfigurationFuture{
  1869  						Future: &types.UpdateFutureProduct{
  1870  							QuoteName: "VUSD",
  1871  							DataSourceSpecForSettlementData: *datasource.NewDefinition(
  1872  								datasource.ContentTypeOracle,
  1873  							).SetOracleConfig(
  1874  								&signedoracle.SpecConfiguration{
  1875  									Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)},
  1876  									Filters: []*dstypes.SpecFilter{
  1877  										{
  1878  											Key: &dstypes.SpecPropertyKey{
  1879  												Name: "prices.ETH.value",
  1880  												Type: datapb.PropertyKey_TYPE_INTEGER,
  1881  											},
  1882  											Conditions: []*dstypes.SpecCondition{},
  1883  										},
  1884  									},
  1885  								},
  1886  							),
  1887  							DataSourceSpecForTradingTermination: *dt,
  1888  							DataSourceSpecBinding:               termBinding,
  1889  						},
  1890  					},
  1891  				},
  1892  				RiskParameters: &types.UpdateMarketConfigurationLogNormal{
  1893  					LogNormal: &types.LogNormalRiskModel{
  1894  						RiskAversionParameter: num.DecimalFromFloat(0.01),
  1895  						Tau:                   num.DecimalFromFloat(0.00011407711613050422),
  1896  						Params: &types.LogNormalModelParams{
  1897  							Mu:    num.DecimalZero(),
  1898  							R:     num.DecimalFromFloat(0.016),
  1899  							Sigma: num.DecimalFromFloat(0.09),
  1900  						},
  1901  					},
  1902  				},
  1903  				Metadata: []string{"asset_class:fx/crypto", "product:futures"},
  1904  				LiquiditySLAParameters: &types.LiquiditySLAParams{
  1905  					PriceRange:                  num.DecimalFromFloat(0.95),
  1906  					CommitmentMinTimeFraction:   num.NewDecimalFromFloat(0.5),
  1907  					PerformanceHysteresisEpochs: 4,
  1908  					SlaCompetitionFactor:        num.NewDecimalFromFloat(0.5),
  1909  				},
  1910  				LinearSlippageFactor:    num.DecimalFromFloat(0.1),
  1911  				QuadraticSlippageFactor: num.DecimalFromFloat(0.1),
  1912  				LiquidationStrategy: &types.LiquidationStrategy{
  1913  					DisposalTimeStep:    10 * time.Second,
  1914  					DisposalFraction:    num.DecimalFromFloat(0.1),
  1915  					FullDisposalSize:    20,
  1916  					MaxFractionConsumed: num.DecimalFromFloat(0.01),
  1917  					DisposalSlippage:    num.DecimalFromFloat(0.1),
  1918  				},
  1919  				TickSize: num.UintOne(),
  1920  			},
  1921  		},
  1922  	}
  1923  }
  1924  
  1925  func (e *tstEngine) submitProposal(t *testing.T, proposal types.Proposal) (*governance.ToSubmit, error) {
  1926  	t.Helper()
  1927  	return e.SubmitProposal(
  1928  		context.Background(),
  1929  		*types.ProposalSubmissionFromProposal(&proposal),
  1930  		proposal.ID,
  1931  		proposal.Party,
  1932  	)
  1933  }
  1934  
  1935  func (e *tstEngine) submitBatchProposal(
  1936  	t *testing.T,
  1937  	submission types.BatchProposalSubmission,
  1938  	proposalID, party string,
  1939  ) ([]*governance.ToSubmit, error) {
  1940  	t.Helper()
  1941  	return e.SubmitBatchProposal(
  1942  		context.Background(),
  1943  		submission,
  1944  		proposalID,
  1945  		party,
  1946  	)
  1947  }
  1948  
  1949  func (e *tstEngine) addYesVote(t *testing.T, party, proposal string) error {
  1950  	t.Helper()
  1951  	return e.AddVote(context.Background(), types.VoteSubmission{
  1952  		ProposalID: proposal,
  1953  		Value:      types.VoteValueYes,
  1954  	}, party)
  1955  }
  1956  
  1957  func (e *tstEngine) addNoVote(t *testing.T, party, proposal string) error {
  1958  	t.Helper()
  1959  	return e.AddVote(context.Background(), types.VoteSubmission{
  1960  		ProposalID: proposal,
  1961  		Value:      types.VoteValueNo,
  1962  	}, party)
  1963  }
  1964  
  1965  func (e *tstEngine) newValidPartyTimes(partyID string, balance uint64, _ int) *types.Party {
  1966  	// is called with 0 times, which messes up the expected calls when adding votes
  1967  	e.tokenBal[partyID] = balance
  1968  	return &types.Party{Id: partyID}
  1969  }
  1970  
  1971  func (e *tstEngine) newValidParty(partyID string, balance uint64) *types.Party {
  1972  	return e.newValidPartyTimes(partyID, balance, 1)
  1973  }
  1974  
  1975  func (e *tstEngine) newProposalID() string {
  1976  	e.proposalCounter++
  1977  	return fmt.Sprintf("proposal-id-%d", e.proposalCounter)
  1978  }
  1979  
  1980  func (e *tstEngine) newBatchSubmission(
  1981  	closingTimestamp int64,
  1982  	proposals ...types.Proposal,
  1983  ) types.BatchProposalSubmission {
  1984  	id := e.newProposalID()
  1985  
  1986  	sub := types.BatchProposalSubmission{
  1987  		Reference: id,
  1988  		Terms: &types.BatchProposalTerms{
  1989  			ClosingTimestamp: closingTimestamp,
  1990  		},
  1991  		Rationale: &types.ProposalRationale{
  1992  			Description: "some description",
  1993  		},
  1994  	}
  1995  
  1996  	for _, proposal := range proposals {
  1997  		sub.Terms.Changes = append(sub.Terms.Changes, types.BatchProposalChange{
  1998  			ID:             proposal.ID,
  1999  			Change:         proposal.Terms.Change,
  2000  			EnactmentTime:  proposal.Terms.EnactmentTimestamp,
  2001  			ValidationTime: proposal.Terms.ValidationTimestamp,
  2002  		})
  2003  	}
  2004  
  2005  	return sub
  2006  }
  2007  
  2008  //nolint:unparam
  2009  func (e *tstEngine) newProposalForNewPerpsMarket(
  2010  	partyID string,
  2011  	now time.Time,
  2012  	termFilter *dstypes.SpecFilter,
  2013  	termBinding *datasource.SpecBindingForPerps,
  2014  	termExt bool,
  2015  ) types.Proposal {
  2016  	id := e.newProposalID()
  2017  	return types.Proposal{
  2018  		ID:        id,
  2019  		Reference: "ref-" + id,
  2020  		Party:     partyID,
  2021  		State:     types.ProposalStateOpen,
  2022  		Terms: &types.ProposalTerms{
  2023  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2024  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2025  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2026  			Change:              newPerpsMarketTerms(termFilter, termBinding),
  2027  		},
  2028  		Rationale: &types.ProposalRationale{
  2029  			Description: "some description",
  2030  		},
  2031  	}
  2032  }
  2033  
  2034  func (e *tstEngine) newProposalForCapped(
  2035  	partyID string,
  2036  	now time.Time,
  2037  	_ *dstypes.SpecFilter,
  2038  	_ *datasource.SpecBindingForFuture,
  2039  	_ bool,
  2040  	fCap *types.FutureCap,
  2041  ) types.Proposal {
  2042  	return e.getMarketProposal(partyID, now, nil, nil, true, fCap)
  2043  }
  2044  
  2045  func (e *tstEngine) newProposalForNewMarket(
  2046  	partyID string,
  2047  	now time.Time,
  2048  	termFilter *dstypes.SpecFilter,
  2049  	termBinding *datasource.SpecBindingForFuture,
  2050  	termExt bool,
  2051  ) types.Proposal {
  2052  	return e.getMarketProposal(partyID, now, termFilter, termBinding, termExt, nil)
  2053  }
  2054  
  2055  func (e *tstEngine) getMarketProposal(
  2056  	partyID string,
  2057  	now time.Time,
  2058  	termFilter *dstypes.SpecFilter,
  2059  	termBinding *datasource.SpecBindingForFuture,
  2060  	termExt bool,
  2061  	fCap *types.FutureCap,
  2062  ) types.Proposal {
  2063  	id := e.newProposalID()
  2064  	return types.Proposal{
  2065  		ID:        id,
  2066  		Reference: "ref-" + id,
  2067  		Party:     partyID,
  2068  		State:     types.ProposalStateOpen,
  2069  		Terms: &types.ProposalTerms{
  2070  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2071  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2072  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2073  			Change:              newMarketTerms(termFilter, termBinding, termExt, nil, fCap),
  2074  		},
  2075  		Rationale: &types.ProposalRationale{
  2076  			Description: "some description",
  2077  		},
  2078  	}
  2079  }
  2080  
  2081  func (e *tstEngine) newProposalForSuccessorMarket(partyID string, now time.Time, termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool, successor *types.SuccessorConfig) types.Proposal {
  2082  	id := e.newProposalID()
  2083  	return types.Proposal{
  2084  		ID:        id,
  2085  		Reference: "ref-" + id,
  2086  		Party:     partyID,
  2087  		State:     types.ProposalStateOpen,
  2088  		Terms: &types.ProposalTerms{
  2089  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2090  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2091  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2092  			Change:              newMarketTerms(termFilter, termBinding, termExt, successor, nil),
  2093  		},
  2094  		Rationale: &types.ProposalRationale{
  2095  			Description: "some description",
  2096  		},
  2097  	}
  2098  }
  2099  
  2100  func (e *tstEngine) newProposalForUpdateMarketState(partyID string, now time.Time, updateType types.MarketStateUpdateType, price *num.Uint) types.Proposal {
  2101  	id := e.newProposalID()
  2102  	return types.Proposal{
  2103  		ID:        id,
  2104  		Reference: "ref-" + id,
  2105  		Party:     partyID,
  2106  		State:     types.ProposalStateOpen,
  2107  		Terms: &types.ProposalTerms{
  2108  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2109  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2110  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2111  			Change:              newUpdateMarketState(updateType, vgrand.RandomStr(5), price),
  2112  		},
  2113  		Rationale: &types.ProposalRationale{
  2114  			Description: "some description",
  2115  		},
  2116  	}
  2117  }
  2118  
  2119  func (e *tstEngine) newProposalForNewSpotMarket(partyID string, now time.Time) types.Proposal {
  2120  	id := e.newProposalID()
  2121  	return types.Proposal{
  2122  		ID:        id,
  2123  		Reference: "ref-" + id,
  2124  		Party:     partyID,
  2125  		State:     types.ProposalStateOpen,
  2126  		Terms: &types.ProposalTerms{
  2127  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2128  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2129  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2130  			Change:              newSpotMarketTerms(),
  2131  		},
  2132  		Rationale: &types.ProposalRationale{
  2133  			Description: "some description",
  2134  		},
  2135  	}
  2136  }
  2137  
  2138  func (e *tstEngine) newProposalForSpotMarketUpdate(marketID, partyID string, now time.Time) types.Proposal {
  2139  	id := e.newProposalID()
  2140  	prop := types.Proposal{
  2141  		ID:        id,
  2142  		Reference: "ref-" + id,
  2143  		Party:     partyID,
  2144  		State:     types.ProposalStateOpen,
  2145  		Terms: &types.ProposalTerms{
  2146  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2147  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2148  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2149  			Change:              updateSpotMarketTerms(),
  2150  		},
  2151  		Rationale: &types.ProposalRationale{
  2152  			Description: "some description",
  2153  		},
  2154  	}
  2155  	switch p := prop.Terms.Change.(type) {
  2156  	case *types.ProposalTermsUpdateSpotMarket:
  2157  		p.UpdateSpotMarket.MarketID = marketID
  2158  	}
  2159  	return prop
  2160  }
  2161  
  2162  func (e *tstEngine) newProposalForMarketUpdate(marketID, partyID string, now time.Time, termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool) types.Proposal {
  2163  	id := e.newProposalID()
  2164  	prop := types.Proposal{
  2165  		ID:        id,
  2166  		Reference: "ref-" + id,
  2167  		Party:     partyID,
  2168  		State:     types.ProposalStateOpen,
  2169  		Terms: &types.ProposalTerms{
  2170  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2171  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2172  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
  2173  			Change:              updateMarketTerms(termFilter, termBinding, termExt),
  2174  		},
  2175  		Rationale: &types.ProposalRationale{
  2176  			Description: "some description",
  2177  		},
  2178  	}
  2179  	switch p := prop.Terms.Change.(type) {
  2180  	case *types.ProposalTermsUpdateMarket:
  2181  		p.UpdateMarket.MarketID = marketID
  2182  	}
  2183  	return prop
  2184  }
  2185  
  2186  func (e *tstEngine) newProposalForReferralProgramUpdate(partyID string, now time.Time, configuration *types.ReferralProgramChanges) types.Proposal {
  2187  	id := e.newProposalID()
  2188  	prop := types.Proposal{
  2189  		ID:        id,
  2190  		Reference: "ref-" + id,
  2191  		Party:     partyID,
  2192  		State:     types.ProposalStateOpen,
  2193  		Terms: &types.ProposalTerms{
  2194  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2195  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2196  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
  2197  			Change: &types.ProposalTermsUpdateReferralProgram{
  2198  				UpdateReferralProgram: &types.UpdateReferralProgram{
  2199  					Changes: configuration,
  2200  				},
  2201  			},
  2202  		},
  2203  		Rationale: &types.ProposalRationale{
  2204  			Description: "some description",
  2205  		},
  2206  	}
  2207  	return prop
  2208  }
  2209  
  2210  func (e *tstEngine) newProposalForVolumeRebateProgramUpdate(partyID string, now time.Time, configuration *types.VolumeRebateProgramChanges) types.Proposal {
  2211  	id := e.newProposalID()
  2212  	prop := types.Proposal{
  2213  		ID:        id,
  2214  		Reference: "ref-" + id,
  2215  		Party:     partyID,
  2216  		State:     types.ProposalStateOpen,
  2217  		Terms: &types.ProposalTerms{
  2218  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2219  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2220  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
  2221  			Change: &types.ProposalTermsUpdateVolumeRebateProgram{
  2222  				UpdateVolumeRebateProgram: &types.UpdateVolumeRebateProgram{
  2223  					Changes: configuration,
  2224  				},
  2225  			},
  2226  		},
  2227  		Rationale: &types.ProposalRationale{
  2228  			Description: "some description",
  2229  		},
  2230  	}
  2231  	return prop
  2232  }
  2233  
  2234  func (e *tstEngine) newProposalForNewProtocolAutomatedPurchase(partyID string, now time.Time, configuration *types.NewProtocolAutomatedPurchaseChanges) types.Proposal {
  2235  	id := e.newProposalID()
  2236  	prop := types.Proposal{
  2237  		ID:        id,
  2238  		Reference: "ref-" + id,
  2239  		Party:     partyID,
  2240  		State:     types.ProposalStateOpen,
  2241  		Terms: &types.ProposalTerms{
  2242  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2243  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2244  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
  2245  			Change: &types.ProposalTermsNewProtocolAutomatedPurchase{
  2246  				NewProtocolAutomatedPurchase: &types.NewProtocolAutomatedPurchase{
  2247  					Changes: configuration,
  2248  				},
  2249  			},
  2250  		},
  2251  		Rationale: &types.ProposalRationale{
  2252  			Description: "some description",
  2253  		},
  2254  	}
  2255  	return prop
  2256  }
  2257  
  2258  func (e *tstEngine) newProposalForVolumeDiscountProgramUpdate(partyID string, now time.Time, configuration *types.VolumeDiscountProgramChanges) types.Proposal {
  2259  	id := e.newProposalID()
  2260  	prop := types.Proposal{
  2261  		ID:        id,
  2262  		Reference: "ref-" + id,
  2263  		Party:     partyID,
  2264  		State:     types.ProposalStateOpen,
  2265  		Terms: &types.ProposalTerms{
  2266  			ClosingTimestamp:    now.Add(96 * time.Hour).Unix(),
  2267  			EnactmentTimestamp:  now.Add(4 * 48 * time.Hour).Unix(),
  2268  			ValidationTimestamp: now.Add(2 * time.Hour).Unix(),
  2269  			Change: &types.ProposalTermsUpdateVolumeDiscountProgram{
  2270  				UpdateVolumeDiscountProgram: &types.UpdateVolumeDiscountProgram{
  2271  					Changes: configuration,
  2272  				},
  2273  			},
  2274  		},
  2275  		Rationale: &types.ProposalRationale{
  2276  			Description: "some description",
  2277  		},
  2278  	}
  2279  	return prop
  2280  }
  2281  
  2282  func (e *tstEngine) newProposalForNewAsset(partyID string, now time.Time) types.Proposal {
  2283  	id := e.newProposalID()
  2284  	return types.Proposal{
  2285  		ID:        id,
  2286  		Reference: "ref-" + id,
  2287  		Party:     partyID,
  2288  		State:     types.ProposalStateOpen,
  2289  		Terms: &types.ProposalTerms{
  2290  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2291  			EnactmentTimestamp:  now.Add(2 * 48 * time.Hour).Unix(),
  2292  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2293  			Change:              newAssetTerms(),
  2294  		},
  2295  		Rationale: &types.ProposalRationale{
  2296  			Description: "some description",
  2297  		},
  2298  	}
  2299  }
  2300  
  2301  func (e *tstEngine) newFreeformProposal(partyID string, now time.Time) types.Proposal {
  2302  	id := e.newProposalID()
  2303  	return types.Proposal{
  2304  		ID:        id,
  2305  		Reference: "ref-" + id,
  2306  		Party:     partyID,
  2307  		State:     types.ProposalStateOpen,
  2308  		Terms: &types.ProposalTerms{
  2309  			ClosingTimestamp:    now.Add(48 * time.Hour).Unix(),
  2310  			ValidationTimestamp: now.Add(1 * time.Hour).Unix(),
  2311  			Change:              newFreeformTerms(),
  2312  		},
  2313  		Rationale: &types.ProposalRationale{
  2314  			Title:       "https://example.com",
  2315  			Description: "Test my freeform proposal",
  2316  		},
  2317  	}
  2318  }
  2319  
  2320  func (e *tstEngine) expectTotalGovernanceTokenFromVoteEvents(t *testing.T, weight, balance string) {
  2321  	t.Helper()
  2322  	e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) {
  2323  		v, ok := evts[0].(*events.Vote)
  2324  		require.True(t, ok)
  2325  		assert.Equal(t, weight, v.TotalGovernanceTokenWeight())
  2326  		assert.Equal(t, balance, v.TotalGovernanceTokenBalance())
  2327  	})
  2328  }
  2329  
  2330  func (e *tstEngine) expectVoteEvents(t *testing.T) {
  2331  	t.Helper()
  2332  	e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) {
  2333  		_, ok := evts[0].(*events.Vote)
  2334  		require.True(t, ok)
  2335  	})
  2336  }
  2337  
  2338  func (e *tstEngine) expectPassedProposalEvent(t *testing.T, proposal string) {
  2339  	t.Helper()
  2340  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(evt events.Event) {
  2341  		pe, ok := evt.(*events.Proposal)
  2342  		require.True(t, ok)
  2343  		p := pe.Proposal()
  2344  		assert.Equal(t, types.ProposalStatePassed.String(), p.State.String())
  2345  		assert.Equal(t, proposal, p.Id)
  2346  	})
  2347  }
  2348  
  2349  func (e *tstEngine) expectDeclinedProposalEvent(t *testing.T, proposal string, reason types.ProposalError) {
  2350  	t.Helper()
  2351  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(evt events.Event) {
  2352  		pe, ok := evt.(*events.Proposal)
  2353  		require.True(t, ok)
  2354  		p := pe.Proposal()
  2355  		assert.Equal(t, types.ProposalStateDeclined.String(), p.State.String())
  2356  		assert.Equal(t, proposal, p.Id)
  2357  		assert.Equal(t, reason.String(), p.Reason.String())
  2358  	})
  2359  }
  2360  
  2361  func (e *tstEngine) expectOpenProposalEvent(t *testing.T, party, proposal string) {
  2362  	t.Helper()
  2363  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(ev events.Event) {
  2364  		pe, ok := ev.(*events.Proposal)
  2365  		require.True(t, ok)
  2366  		p := pe.Proposal()
  2367  		reason := types.ProposalErrorUnspecified
  2368  		if p.Reason != nil {
  2369  			reason = *p.Reason
  2370  		}
  2371  		errDetails := ""
  2372  		if p.ErrorDetails != nil {
  2373  			errDetails = *p.ErrorDetails
  2374  		}
  2375  		assert.Equal(t, types.ProposalStateOpen.String(), p.State.String(), fmt.Sprintf("reason: %v, details: %s", reason, errDetails))
  2376  		assert.Equal(t, party, p.PartyId)
  2377  		assert.Equal(t, proposal, p.Id)
  2378  	})
  2379  }
  2380  
  2381  func (e *tstEngine) expectProposalWaitingForNodeVoteEvent(t *testing.T, party, proposal string) {
  2382  	t.Helper()
  2383  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(ev events.Event) {
  2384  		pe, ok := ev.(*events.Proposal)
  2385  		require.True(t, ok)
  2386  		p := pe.Proposal()
  2387  		assert.Equal(t, types.ProposalStateWaitingForNodeVote.String(), p.State.String())
  2388  		assert.Equal(t, party, p.PartyId)
  2389  		assert.Equal(t, proposal, p.Id)
  2390  	})
  2391  }
  2392  
  2393  func (e *tstEngine) expectRejectedProposalEvent(t *testing.T, partyID, proposalID string, reason types.ProposalError) {
  2394  	t.Helper()
  2395  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(e events.Event) {
  2396  		pe, ok := e.(*events.Proposal)
  2397  		require.True(t, ok)
  2398  		p := pe.Proposal()
  2399  		assert.Equal(t, proposalID, p.Id)
  2400  		assert.Equal(t, partyID, p.PartyId)
  2401  		assert.Equal(t, types.ProposalStateRejected.String(), p.State.String())
  2402  		assert.Equal(t, reason.String(), p.Reason.String())
  2403  	})
  2404  }
  2405  
  2406  type expectedProposal struct {
  2407  	partyID    string
  2408  	proposalID string
  2409  	state      types.ProposalState
  2410  	reason     types.ProposalError
  2411  }
  2412  
  2413  func (e *tstEngine) expectProposalEvents(t *testing.T, expected []expectedProposal) {
  2414  	t.Helper()
  2415  	e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) {
  2416  		assert.GreaterOrEqual(t, len(evts), len(expected))
  2417  		for i, expect := range expected {
  2418  			e := evts[i]
  2419  			pe, ok := e.(*events.Proposal)
  2420  			require.True(t, ok)
  2421  			p := pe.Proposal()
  2422  
  2423  			assert.Equal(t, expect.proposalID, p.Id)
  2424  			assert.Equal(t, expect.partyID, p.PartyId)
  2425  			assert.Equal(t, expect.state.String(), p.State.String())
  2426  			if p.Reason != nil {
  2427  				assert.Equal(t, expect.reason.String(), p.Reason.String())
  2428  			}
  2429  		}
  2430  	})
  2431  }
  2432  
  2433  func (e *tstEngine) expectVoteEvent(t *testing.T, party, proposal string) {
  2434  	t.Helper()
  2435  	e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(e events.Event) {
  2436  		ve, ok := e.(*events.Vote)
  2437  		require.True(t, ok)
  2438  		vote := ve.Vote()
  2439  		assert.Equal(t, proposal, vote.ProposalId)
  2440  		assert.Equal(t, party, vote.PartyId)
  2441  	})
  2442  }
  2443  
  2444  func (e *tstEngine) expectRestoredProposals(t *testing.T, proposalIDs []string) {
  2445  	t.Helper()
  2446  	e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(es []events.Event) {
  2447  		if len(es) == 0 {
  2448  			t.Errorf("expecting %d proposals to be restored, found %d", len(proposalIDs), len(es))
  2449  		}
  2450  
  2451  		for i, e := range es {
  2452  			pe, ok := e.(*events.Proposal)
  2453  			require.True(t, ok)
  2454  			assert.Equal(t, proposalIDs[i], pe.ProposalID())
  2455  		}
  2456  	})
  2457  }
  2458  
  2459  func (e *tstEngine) ensureStakingAssetTotalSupply(t *testing.T, supply uint64) {
  2460  	t.Helper()
  2461  	e.accounts.EXPECT().GetStakingAssetTotalSupply().Times(1).Return(num.NewUint(supply))
  2462  }
  2463  
  2464  func (e *tstEngine) ensureTokenBalanceForParty(t *testing.T, party string, balance uint64) {
  2465  	t.Helper()
  2466  	e.tokenBal[party] = balance
  2467  }
  2468  
  2469  func (e *tstEngine) ensureAllAssetEnabled(t *testing.T) {
  2470  	t.Helper()
  2471  	details := newAssetTerms()
  2472  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(id string) (*assets.Asset, error) {
  2473  		ret := assets.NewAsset(builtin.New(id, details.NewAsset.Changes))
  2474  		return ret, nil
  2475  	})
  2476  	e.assets.EXPECT().IsEnabled(gomock.Any()).AnyTimes().Return(true)
  2477  }
  2478  
  2479  func (e *tstEngine) ensureAllAssetEnabledWithDP(t *testing.T, base, quote string, baseDecimals, quoteDecimals uint64) {
  2480  	t.Helper()
  2481  	baseDetails := newAssetTerms()
  2482  	baseDetails.NewAsset.Changes.Symbol = base
  2483  	baseDetails.NewAsset.Changes.Decimals = baseDecimals
  2484  
  2485  	quoteDetails := newAssetTerms()
  2486  	quoteDetails.NewAsset.Changes.Symbol = quote
  2487  	quoteDetails.NewAsset.Changes.Decimals = quoteDecimals
  2488  
  2489  	e.assets.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(id string) (*assets.Asset, error) {
  2490  		if id == base {
  2491  			ret := assets.NewAsset(builtin.New(id, baseDetails.NewAsset.Changes))
  2492  			return ret, nil
  2493  		} else {
  2494  			ret := assets.NewAsset(builtin.New(id, quoteDetails.NewAsset.Changes))
  2495  			return ret, nil
  2496  		}
  2497  	})
  2498  	e.assets.EXPECT().IsEnabled(gomock.Any()).AnyTimes().Return(true)
  2499  }
  2500  
  2501  func (e *tstEngine) ensureEquityLikeShareForMarketAndParty(t *testing.T, market, party string, share float64) {
  2502  	t.Helper()
  2503  	mels, ok := e.els[market]
  2504  	if !ok {
  2505  		mels = map[string]float64{}
  2506  	}
  2507  	mels[party] = share
  2508  	e.els[market] = mels
  2509  	e.markets.EXPECT().
  2510  		GetEquityLikeShareForMarketAndParty(market, party).
  2511  		AnyTimes().
  2512  		Return(num.DecimalFromFloat(share), true)
  2513  }
  2514  
  2515  func (e *tstEngine) ensureGetMarket(t *testing.T, marketID string, market types.Market) {
  2516  	t.Helper()
  2517  	e.markets.EXPECT().
  2518  		GetMarket(marketID, gomock.Any()).
  2519  		Times(1).
  2520  		Return(market, true)
  2521  }
  2522  
  2523  func (e *tstEngine) ensureGetMarketFuture(t *testing.T, marketID string) {
  2524  	t.Helper()
  2525  	e.ensureGetMarket(t, marketID, types.Market{
  2526  		TradableInstrument: &types.TradableInstrument{
  2527  			Instrument: &types.Instrument{
  2528  				Product: &types.InstrumentFuture{
  2529  					Future: &types.Future{},
  2530  				},
  2531  			},
  2532  		},
  2533  	},
  2534  	)
  2535  }
  2536  
  2537  func (e *tstEngine) ensureGetMarketSpot(t *testing.T, marketID string) {
  2538  	t.Helper()
  2539  	e.ensureGetMarket(t, marketID, types.Market{
  2540  		TradableInstrument: &types.TradableInstrument{
  2541  			Instrument: &types.Instrument{
  2542  				Product: &types.InstrumentSpot{
  2543  					Spot: &types.Spot{},
  2544  				},
  2545  			},
  2546  		},
  2547  	},
  2548  	)
  2549  }
  2550  
  2551  func (e *tstEngine) ensureGetMarketPerpetual(t *testing.T, marketID string) {
  2552  	t.Helper()
  2553  	e.ensureGetMarket(t, marketID, types.Market{
  2554  		TradableInstrument: &types.TradableInstrument{
  2555  			Instrument: &types.Instrument{
  2556  				Product: &types.InstrumentPerps{
  2557  					Perps: &types.Perps{},
  2558  				},
  2559  			},
  2560  		},
  2561  	},
  2562  	)
  2563  }
  2564  
  2565  func (e *tstEngine) expectGetMarketState(t *testing.T, marketID string) {
  2566  	t.Helper()
  2567  	e.markets.EXPECT().GetMarketState(marketID).AnyTimes().Return(types.MarketStateActive, nil)
  2568  }
  2569  
  2570  func (e *tstEngine) ensureNonExistingMarket(t *testing.T, market string) {
  2571  	t.Helper()
  2572  	e.markets.EXPECT().MarketExists(market).Times(1).Return(false)
  2573  }
  2574  
  2575  func (e *tstEngine) ensureExistingMarket(t *testing.T, market string) {
  2576  	t.Helper()
  2577  	e.markets.EXPECT().MarketExists(market).Times(1).Return(true)
  2578  }
  2579  
  2580  func (e *tstEngine) ensureNoAccountForParty(t *testing.T, partyID string) {
  2581  	t.Helper()
  2582  	delete(e.tokenBal, partyID)
  2583  }
  2584  
  2585  func (e *tstEngine) ensureNetworkParameter(t *testing.T, key, value string) {
  2586  	t.Helper()
  2587  	e.broker.EXPECT().Send(gomock.Any()).Times(1)
  2588  	if err := e.netp.Update(context.Background(), key, value); err != nil {
  2589  		t.Fatalf("failed to set %s parameter: %v", key, err)
  2590  	}
  2591  }