code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/proposals_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 sqlstore_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	dstypes "code.vegaprotocol.io/vega/core/datasource/common"
    25  	"code.vegaprotocol.io/vega/datanode/entities"
    26  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	"code.vegaprotocol.io/vega/protos/vega"
    29  	datav1 "code.vegaprotocol.io/vega/protos/vega/data/v1"
    30  
    31  	"github.com/google/go-cmp/cmp"
    32  	"github.com/google/go-cmp/cmp/cmpopts"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  var (
    38  	testProposals       []entities.Proposal
    39  	proposalTestParties []entities.Party
    40  )
    41  
    42  func addTestProposal(
    43  	t *testing.T,
    44  	ctx context.Context,
    45  	ps *sqlstore.Proposals,
    46  	id string,
    47  	party entities.Party,
    48  	reference string,
    49  	block entities.Block,
    50  	state entities.ProposalState,
    51  	rationale entities.ProposalRationale,
    52  	terms entities.ProposalTerms,
    53  	reason entities.ProposalError,
    54  	batchID *string,
    55  	batchTerms entities.BatchProposalTerms,
    56  ) entities.Proposal {
    57  	t.Helper()
    58  
    59  	var batchProposalID entities.ProposalID
    60  	if batchID != nil {
    61  		batchProposalID = entities.ProposalID(*batchID)
    62  	}
    63  
    64  	p := entities.Proposal{
    65  		ID:                      entities.ProposalID(id),
    66  		BatchID:                 batchProposalID,
    67  		PartyID:                 party.ID,
    68  		Reference:               reference,
    69  		Terms:                   terms,
    70  		BatchTerms:              batchTerms,
    71  		State:                   state,
    72  		VegaTime:                block.VegaTime,
    73  		ProposalTime:            block.VegaTime,
    74  		Rationale:               rationale,
    75  		Reason:                  reason,
    76  		RequiredMajority:        num.MustDecimalFromString("0.5"),
    77  		RequiredParticipation:   num.MustDecimalFromString("0.7"),
    78  		RequiredLPMajority:      nil,
    79  		RequiredLPParticipation: nil,
    80  		TxHash:                  generateTxHash(),
    81  	}
    82  	assert.NoError(t, ps.Add(ctx, p))
    83  	return p
    84  }
    85  
    86  func proposalLessThan(x, y entities.Proposal) bool {
    87  	return x.ID.String() < y.ID.String()
    88  }
    89  
    90  func assertProposalsMatch(t *testing.T, expected, actual []entities.Proposal) {
    91  	t.Helper()
    92  	sortProposals := cmpopts.SortSlices(proposalLessThan)
    93  	ignoreProtoState := cmpopts.IgnoreUnexported(
    94  		vega.ProposalTerms{},
    95  		vega.BatchProposalTerms{},
    96  		vega.BatchProposalTermsChange{},
    97  		vega.ProposalRationale{},
    98  		vega.NewMarket{},
    99  		vega.NewAsset{},
   100  		vega.UpdateAsset{},
   101  		vega.NewMarketConfiguration{},
   102  		vega.SuccessorConfiguration{},
   103  	)
   104  	assert.Empty(t, cmp.Diff(actual, expected, sortProposals, ignoreProtoState))
   105  }
   106  
   107  func assertProposalMatch(t *testing.T, expected, actual entities.Proposal) {
   108  	t.Helper()
   109  	ignoreProtoState := cmpopts.IgnoreUnexported(
   110  		vega.ProposalTerms{},
   111  		vega.BatchProposalTerms{},
   112  		vega.BatchProposalTermsChange{},
   113  		vega.ProposalRationale{},
   114  		vega.NewMarket{},
   115  		vega.NewAsset{},
   116  		vega.UpdateAsset{},
   117  		vega.NewMarketConfiguration{},
   118  		vega.SuccessorConfiguration{},
   119  	)
   120  	assert.Empty(t, cmp.Diff(actual, expected, ignoreProtoState))
   121  }
   122  
   123  func TestProposals(t *testing.T) {
   124  	ctx := tempTransaction(t)
   125  
   126  	partyStore := sqlstore.NewParties(connectionSource)
   127  	propStore := sqlstore.NewProposals(connectionSource)
   128  	blockStore := sqlstore.NewBlocks(connectionSource)
   129  	block1 := addTestBlock(t, ctx, blockStore)
   130  
   131  	party1 := addTestParty(t, ctx, partyStore, block1)
   132  	party2 := addTestParty(t, ctx, partyStore, block1)
   133  	rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}
   134  	rationale2 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl2.com", Description: "desc"}}
   135  	terms1 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}}
   136  	terms2 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewAsset{NewAsset: &vega.NewAsset{}}}}
   137  	id1 := GenerateID()
   138  	id2 := GenerateID()
   139  
   140  	reference1 := GenerateID()
   141  	reference2 := GenerateID()
   142  	prop1 := addTestProposal(t, ctx, propStore, id1, party1, reference1, block1, entities.ProposalStateEnacted, rationale1, terms1, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
   143  	prop2 := addTestProposal(t, ctx, propStore, id2, party2, reference2, block1, entities.ProposalStateEnacted, rationale2, terms2, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
   144  
   145  	party1ID := party1.ID.String()
   146  	prop1ID := prop1.ID.String()
   147  	propType := &entities.ProposalTypeNewMarket
   148  
   149  	t.Run("GetById", func(t *testing.T) {
   150  		expected := prop1
   151  		actual, err := propStore.GetByID(ctx, prop1ID)
   152  		require.NoError(t, err)
   153  		assertProposalMatch(t, expected, actual)
   154  	})
   155  
   156  	t.Run("GetByTxHash", func(t *testing.T) {
   157  		expected := prop1
   158  		actual, err := propStore.GetByTxHash(ctx, expected.TxHash)
   159  		require.NoError(t, err)
   160  		assertProposalMatch(t, expected, actual[0])
   161  
   162  		expected = prop2
   163  		actual, err = propStore.GetByTxHash(ctx, expected.TxHash)
   164  		require.NoError(t, err)
   165  		assertProposalMatch(t, expected, actual[0])
   166  	})
   167  
   168  	t.Run("GetByReference", func(t *testing.T) {
   169  		expected := prop2
   170  		actual, err := propStore.GetByReference(ctx, prop2.Reference)
   171  		require.NoError(t, err)
   172  		assertProposalMatch(t, expected, actual)
   173  	})
   174  
   175  	t.Run("GetInState", func(t *testing.T) {
   176  		enacted := entities.ProposalStateEnacted
   177  		expected := []entities.Proposal{prop1, prop2}
   178  		actual, _, err := propStore.Get(ctx, &enacted, nil, nil, entities.CursorPagination{})
   179  		require.NoError(t, err)
   180  		assertProposalsMatch(t, expected, actual)
   181  	})
   182  
   183  	t.Run("GetByParty", func(t *testing.T) {
   184  		expected := []entities.Proposal{prop1}
   185  		actual, _, err := propStore.Get(ctx, nil, &party1ID, nil, entities.CursorPagination{})
   186  		require.NoError(t, err)
   187  		assertProposalsMatch(t, expected, actual)
   188  	})
   189  
   190  	t.Run("GetByType", func(t *testing.T) {
   191  		expected := []entities.Proposal{prop1}
   192  		actual, _, err := propStore.Get(ctx, nil, nil, propType, entities.CursorPagination{})
   193  		require.NoError(t, err)
   194  		assertProposalsMatch(t, expected, actual)
   195  	})
   196  
   197  	t.Run("Add with proposal error", func(t *testing.T) {
   198  		propError := entities.ProposalInvalidPerpetualProduct
   199  		expected := addTestProposal(t, ctx, propStore, GenerateID(), party1, reference1, block1, entities.ProposalStateEnacted, rationale1, terms1, propError, nil, entities.BatchProposalTerms{})
   200  		actual, err := propStore.GetByID(ctx, string(expected.ID))
   201  		require.NoError(t, err)
   202  		assert.Equal(t, expected.Reason, actual.Reason)
   203  	})
   204  }
   205  
   206  func newBatchProposalProposal() entities.BatchProposalTerms {
   207  	return entities.BatchProposalTerms{
   208  		BatchProposalTerms: &vega.BatchProposalTerms{
   209  			ClosingTimestamp: 10,
   210  			Changes: []*vega.BatchProposalTermsChange{
   211  				{
   212  					EnactmentTimestamp: 20,
   213  					Change:             &vega.BatchProposalTermsChange_NewMarket{NewMarket: &vega.NewMarket{}},
   214  				},
   215  				{
   216  					EnactmentTimestamp: 30,
   217  					Change:             &vega.BatchProposalTermsChange_UpdateAsset{UpdateAsset: &vega.UpdateAsset{}},
   218  				},
   219  			},
   220  		},
   221  	}
   222  }
   223  
   224  func TestBatchProposals(t *testing.T) {
   225  	ctx := context.Background()
   226  	// We cannot use the tempTransaction for this test due to the fact that the connection gets blocked when
   227  	// we recursively look for proposals that belong in a batch. The use of the transaction prevents another connection being
   228  	// taken from the connection pool, and causes a conn is busy error, we therefore just use a background context for these
   229  	// tests, and make sure we clean up after ourselves instead of rolling back the transaction.
   230  	defer cleanupTestProposals(t)
   231  
   232  	partyStore := sqlstore.NewParties(connectionSource)
   233  	propStore := sqlstore.NewProposals(connectionSource)
   234  	blockStore := sqlstore.NewBlocks(connectionSource)
   235  	block1 := addTestBlock(t, ctx, blockStore)
   236  
   237  	party1 := addTestParty(t, ctx, partyStore, block1)
   238  	party2 := addTestParty(t, ctx, partyStore, block1)
   239  	rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}
   240  	rationale2 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl2.com", Description: "desc"}}
   241  	terms1 := newBatchProposalProposal()
   242  	terms2 := newBatchProposalProposal()
   243  	id1 := GenerateID()
   244  	id2 := GenerateID()
   245  	subId1 := GenerateID()
   246  	subId2 := GenerateID()
   247  	subId3 := GenerateID()
   248  	subId4 := GenerateID()
   249  
   250  	now := time.Now()
   251  
   252  	reference1 := GenerateID()
   253  	reference2 := GenerateID()
   254  	reference3 := GenerateID()
   255  	reference4 := GenerateID()
   256  	reference5 := GenerateID()
   257  	reference6 := GenerateID()
   258  	prop1 := addTestProposal(t, ctx, propStore, id1, party1, reference1, block1, entities.ProposalStateEnacted, rationale1,
   259  		entities.ProposalTerms{}, entities.ProposalErrorUnspecified, nil, terms1)
   260  	prop1.Proposals = append(prop1.Proposals,
   261  		addTestProposal(t, ctx, propStore, subId1, party2, reference3, block1, entities.ProposalStateEnacted, rationale2,
   262  			entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{EnactmentTimestamp: now.Unix(), Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}},
   263  			entities.ProposalErrorUnspecified, &id1, entities.BatchProposalTerms{},
   264  		),
   265  		addTestProposal(t, ctx, propStore, subId2, party2, reference4, block1, entities.ProposalStateEnacted, rationale2,
   266  			entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{EnactmentTimestamp: now.Add(time.Second).Unix(), Change: &vega.ProposalTerms_UpdateAsset{UpdateAsset: &vega.UpdateAsset{}}}},
   267  			entities.ProposalErrorUnspecified, &id1, entities.BatchProposalTerms{},
   268  		),
   269  	)
   270  
   271  	prop2 := addTestProposal(t, ctx, propStore, id2, party2, reference2, block1, entities.ProposalStateEnacted, rationale2,
   272  		entities.ProposalTerms{}, entities.ProposalErrorUnspecified, nil, terms2)
   273  	prop2.Proposals = append(prop2.Proposals,
   274  		addTestProposal(
   275  			t, ctx, propStore, subId3, party2, reference5, block1, entities.ProposalStateEnacted, rationale2,
   276  			entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{EnactmentTimestamp: now.Unix(), Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}},
   277  			entities.ProposalErrorUnspecified, &id2, entities.BatchProposalTerms{},
   278  		),
   279  		addTestProposal(
   280  			t, ctx, propStore, subId4, party2, reference6, block1, entities.ProposalStateEnacted, rationale2,
   281  			entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{EnactmentTimestamp: now.Add(time.Second).Unix(), Change: &vega.ProposalTerms_UpdateAsset{UpdateAsset: &vega.UpdateAsset{}}}},
   282  			entities.ProposalErrorUnspecified, &id2, entities.BatchProposalTerms{},
   283  		),
   284  	)
   285  
   286  	party1ID := party1.ID.String()
   287  	prop1ID := prop1.ID.String()
   288  	propType := &entities.ProposalTypeNewMarket
   289  
   290  	t.Run("GetById batch", func(t *testing.T) {
   291  		expected := prop1
   292  		actual, err := propStore.GetByID(ctx, prop1ID)
   293  		require.NoError(t, err)
   294  		assertProposalMatch(t, expected, actual)
   295  	})
   296  
   297  	t.Run("GetById proposal from batch returns the whole batch", func(t *testing.T) {
   298  		expected := prop1
   299  		actual, err := propStore.GetByID(ctx, string(expected.Proposals[0].ID))
   300  		require.NoError(t, err)
   301  		assertProposalMatch(t, expected, actual)
   302  	})
   303  
   304  	t.Run("GetByTxHash", func(t *testing.T) {
   305  		expected := prop1
   306  		actual, err := propStore.GetByTxHash(ctx, expected.TxHash)
   307  		require.NoError(t, err)
   308  		assertProposalMatch(t, expected, actual[0])
   309  
   310  		expected = prop2
   311  		actual, err = propStore.GetByTxHash(ctx, expected.TxHash)
   312  		require.NoError(t, err)
   313  		assertProposalMatch(t, expected, actual[0])
   314  	})
   315  
   316  	t.Run("GetByReference batch", func(t *testing.T) {
   317  		expected := prop2
   318  		actual, err := propStore.GetByReference(ctx, expected.Reference)
   319  		require.NoError(t, err)
   320  		assertProposalMatch(t, expected, actual)
   321  	})
   322  
   323  	t.Run("GetByReference proposal from batch returns the whole batch", func(t *testing.T) {
   324  		expected := prop2
   325  		actual, err := propStore.GetByReference(ctx, expected.Proposals[0].Reference)
   326  		require.NoError(t, err)
   327  		assertProposalMatch(t, expected, actual)
   328  	})
   329  
   330  	t.Run("GetInState", func(t *testing.T) {
   331  		enacted := entities.ProposalStateEnacted
   332  		expected := []entities.Proposal{prop1, prop2}
   333  		actual, _, err := propStore.Get(ctx, &enacted, nil, nil, entities.CursorPagination{})
   334  		require.NoError(t, err)
   335  		assertProposalsMatch(t, expected, actual)
   336  	})
   337  
   338  	t.Run("GetByParty", func(t *testing.T) {
   339  		expected := []entities.Proposal{prop1}
   340  		actual, _, err := propStore.Get(ctx, nil, &party1ID, nil, entities.CursorPagination{})
   341  		require.NoError(t, err)
   342  		assertProposalsMatch(t, expected, actual)
   343  	})
   344  
   345  	t.Run("GetByType", func(t *testing.T) {
   346  		expected := []entities.Proposal{prop1, prop2}
   347  		actual, _, err := propStore.Get(ctx, nil, nil, propType, entities.CursorPagination{})
   348  		require.NoError(t, err)
   349  		assertProposalsMatch(t, expected, actual)
   350  	})
   351  
   352  	t.Run("Add with proposal error", func(t *testing.T) {
   353  		propError := entities.ProposalInvalidPerpetualProduct
   354  		expected := addTestProposal(t, ctx, propStore, GenerateID(), party1, reference1, block1, entities.ProposalStateEnacted, rationale1, entities.ProposalTerms{}, propError, nil, terms1)
   355  		actual, err := propStore.GetByID(ctx, string(expected.ID))
   356  		require.NoError(t, err)
   357  		assert.Equal(t, expected.Reason, actual.Reason)
   358  	})
   359  }
   360  
   361  func TestProposalCursorPagination(t *testing.T) {
   362  	ctx := context.Background()
   363  	ps := sqlstore.NewProposals(connectionSource)
   364  	testProposals, proposalTestParties = createPaginationTestProposals(t, ctx, ps)
   365  	// We cannot use the tempTransaction for this test due to the fact that the connection gets blocked when
   366  	// we recursively look for proposals that belong in a batch. The use of the transaction prevents another connection being
   367  	// taken from the connection pool, and causes a conn is busy error, we therefore just use a background context for these
   368  	// tests, and make sure we clean up after ourselves instead of rolling back the transaction.
   369  	defer cleanupTestProposals(t)
   370  
   371  	t.Run("should return all proposals when no paging is provided", testProposalCursorPaginationNoPagination)
   372  	t.Run("should return only the first page of proposals when first is provided", testProposalCursorPaginationWithFirst)
   373  	t.Run("should return only the requested page of proposals when first and after is provided", testProposalCursorPaginationWithFirstAndAfter)
   374  	t.Run("should return only the last page of proposals when last is provided", testProposalCursorPaginationWithLast)
   375  	t.Run("should return only the requested page of proposals when last and before is provided", testProposalCursorPaginationWithLastAndBefore)
   376  
   377  	t.Run("should return all proposals when no paging is provided - newest first", testProposalCursorPaginationNoPaginationNewestFirst)
   378  	t.Run("should return only the first page of proposals when first is provided - newest first", testProposalCursorPaginationWithFirstNewestFirst)
   379  	t.Run("should return only the requested page of proposals when first and after is provided - newest first", testProposalCursorPaginationWithFirstAndAfterNewestFirst)
   380  	t.Run("should return only the last page of proposals when last is provided - newest first", testProposalCursorPaginationWithLastNewestFirst)
   381  	t.Run("should return only the requested page of proposals when last and before is provided - newest first", testProposalCursorPaginationWithLastAndBeforeNewestFirst)
   382  
   383  	t.Run("should return all proposals for a given party when no paging is provided", testProposalCursorPaginationNoPaginationByParty)
   384  	t.Run("should return only the first page of proposals for a given party when first is provided", testProposalCursorPaginationWithFirstByParty)
   385  	t.Run("should return only the requested page of proposals for a given party when first and after is provided", testProposalCursorPaginationWithFirstAndAfterByParty)
   386  	t.Run("should return only the last page of proposals for a given party when last is provided", testProposalCursorPaginationWithLastByParty)
   387  	t.Run("should return only the requested page of proposals for a given party when last and before is provided", testProposalCursorPaginationWithLastAndBeforeByParty)
   388  
   389  	t.Run("should return all proposals for a given party when no paging is provided - newest first", testProposalCursorPaginationNoPaginationByPartyNewestFirst)
   390  	t.Run("should return only the first page of proposals for a given party when first is provided - newest first", testProposalCursorPaginationWithFirstByPartyNewestFirst)
   391  	t.Run("should return only the requested page of proposals for a given party when first and after is provided - newest first", testProposalCursorPaginationWithFirstAndAfterByPartyNewestFirst)
   392  	t.Run("should return only the last page of proposals for a given party when last is provided - newest first", testProposalCursorPaginationWithLastByPartyNewestFirst)
   393  	t.Run("should return only the requested page of proposals for a given party when last and before is provided - newest first", testProposalCursorPaginationWithLastAndBeforeByPartyNewestFirst)
   394  
   395  	t.Run("should return only the open proposals if open state is provided in the filter", testProposalCursorPaginationOpenOnly)
   396  	t.Run("should return the specified proposal state if one is provided", testProposalCursorPaginationGivenState)
   397  }
   398  
   399  func testProposalCursorPaginationNoPagination(t *testing.T) {
   400  	ctx := context.Background()
   401  	ps := sqlstore.NewProposals(connectionSource)
   402  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   403  	require.NoError(t, err)
   404  
   405  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   406  	require.NoError(t, err)
   407  	// Proposals should be listed in order of their status, then time, then id
   408  	want := []entities.Proposal{
   409  		testProposals[0],
   410  		testProposals[10],
   411  		testProposals[1],
   412  		testProposals[11],
   413  		testProposals[2],
   414  		testProposals[12],
   415  		testProposals[8],
   416  		testProposals[18],
   417  		testProposals[3],
   418  		testProposals[13],
   419  		testProposals[4],
   420  		testProposals[14],
   421  		testProposals[5],
   422  		testProposals[15],
   423  		testProposals[6],
   424  		testProposals[16],
   425  		testProposals[7],
   426  		testProposals[17],
   427  		testProposals[9],
   428  		testProposals[19],
   429  	}
   430  	assert.Equal(t, want, got)
   431  	assert.Equal(t, entities.PageInfo{
   432  		HasNextPage:     false,
   433  		HasPreviousPage: false,
   434  		StartCursor:     testProposals[0].Cursor().Encode(),
   435  		EndCursor:       testProposals[19].Cursor().Encode(),
   436  	}, pageInfo)
   437  }
   438  
   439  func testProposalCursorPaginationWithFirst(t *testing.T) {
   440  	ctx := context.Background()
   441  
   442  	ps := sqlstore.NewProposals(connectionSource)
   443  	first := int32(3)
   444  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   445  	require.NoError(t, err)
   446  
   447  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   448  	require.NoError(t, err)
   449  	// Proposals should be listed in order of their status, then time, then id
   450  	want := []entities.Proposal{
   451  		testProposals[0],
   452  		testProposals[10],
   453  		testProposals[1],
   454  	}
   455  	assert.Equal(t, want, got)
   456  	assert.Equal(t, entities.PageInfo{
   457  		HasNextPage:     true,
   458  		HasPreviousPage: false,
   459  		StartCursor:     testProposals[0].Cursor().Encode(),
   460  		EndCursor:       testProposals[1].Cursor().Encode(),
   461  	}, pageInfo)
   462  }
   463  
   464  func testProposalCursorPaginationWithFirstAndAfter(t *testing.T) {
   465  	ctx := context.Background()
   466  
   467  	ps := sqlstore.NewProposals(connectionSource)
   468  	first := int32(8)
   469  	after := testProposals[1].Cursor().Encode()
   470  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   471  	require.NoError(t, err)
   472  
   473  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   474  	require.NoError(t, err)
   475  	// Proposals should be listed in order of their status, then time, then id
   476  	want := []entities.Proposal{
   477  		testProposals[11],
   478  		testProposals[2],
   479  		testProposals[12],
   480  		testProposals[8],
   481  		testProposals[18],
   482  		testProposals[3],
   483  		testProposals[13],
   484  		testProposals[4],
   485  	}
   486  	assert.Equal(t, want, got)
   487  	assert.Equal(t, entities.PageInfo{
   488  		HasNextPage:     true,
   489  		HasPreviousPage: true,
   490  		StartCursor:     testProposals[11].Cursor().Encode(),
   491  		EndCursor:       testProposals[4].Cursor().Encode(),
   492  	}, pageInfo)
   493  }
   494  
   495  func testProposalCursorPaginationWithLast(t *testing.T) {
   496  	ctx := context.Background()
   497  
   498  	ps := sqlstore.NewProposals(connectionSource)
   499  	last := int32(3)
   500  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   501  	require.NoError(t, err)
   502  
   503  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   504  	require.NoError(t, err)
   505  	// Proposals should be listed in order of their status, then time, then id
   506  	want := []entities.Proposal{
   507  		testProposals[17],
   508  		testProposals[9],
   509  		testProposals[19],
   510  	}
   511  	assert.Equal(t, want, got)
   512  	assert.Equal(t, entities.PageInfo{
   513  		HasNextPage:     false,
   514  		HasPreviousPage: true,
   515  		StartCursor:     testProposals[17].Cursor().Encode(),
   516  		EndCursor:       testProposals[19].Cursor().Encode(),
   517  	}, pageInfo)
   518  }
   519  
   520  func testProposalCursorPaginationWithLastAndBefore(t *testing.T) {
   521  	ctx := context.Background()
   522  
   523  	ps := sqlstore.NewProposals(connectionSource)
   524  	last := int32(8)
   525  	before := testProposals[5].Cursor().Encode()
   526  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   527  	require.NoError(t, err)
   528  
   529  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   530  	require.NoError(t, err)
   531  	// Proposals should be listed in order of their status, then time, then id
   532  	want := []entities.Proposal{
   533  		testProposals[2],
   534  		testProposals[12],
   535  		testProposals[8],
   536  		testProposals[18],
   537  		testProposals[3],
   538  		testProposals[13],
   539  		testProposals[4],
   540  		testProposals[14],
   541  	}
   542  	assert.Equal(t, want, got)
   543  	assert.Equal(t, entities.PageInfo{
   544  		HasNextPage:     true,
   545  		HasPreviousPage: true,
   546  		StartCursor:     testProposals[2].Cursor().Encode(),
   547  		EndCursor:       testProposals[14].Cursor().Encode(),
   548  	}, pageInfo)
   549  }
   550  
   551  func testProposalCursorPaginationNoPaginationNewestFirst(t *testing.T) {
   552  	ctx := context.Background()
   553  
   554  	ps := sqlstore.NewProposals(connectionSource)
   555  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   556  	require.NoError(t, err)
   557  
   558  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   559  	require.NoError(t, err)
   560  	// Proposals should be listed in order of their status, then time, then id
   561  	want := []entities.Proposal{
   562  		testProposals[18],
   563  		testProposals[8],
   564  		testProposals[12],
   565  		testProposals[2],
   566  		testProposals[11],
   567  		testProposals[1],
   568  		testProposals[10],
   569  		testProposals[0],
   570  		testProposals[19],
   571  		testProposals[9],
   572  		testProposals[17],
   573  		testProposals[7],
   574  		testProposals[16],
   575  		testProposals[6],
   576  		testProposals[15],
   577  		testProposals[5],
   578  		testProposals[14],
   579  		testProposals[4],
   580  		testProposals[13],
   581  		testProposals[3],
   582  	}
   583  	assert.Equal(t, want, got)
   584  	assert.Equal(t, entities.PageInfo{
   585  		HasNextPage:     false,
   586  		HasPreviousPage: false,
   587  		StartCursor:     testProposals[18].Cursor().Encode(),
   588  		EndCursor:       testProposals[3].Cursor().Encode(),
   589  	}, pageInfo)
   590  }
   591  
   592  func testProposalCursorPaginationWithFirstNewestFirst(t *testing.T) {
   593  	ctx := context.Background()
   594  
   595  	ps := sqlstore.NewProposals(connectionSource)
   596  	first := int32(3)
   597  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   598  	require.NoError(t, err)
   599  
   600  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   601  	require.NoError(t, err)
   602  	// Proposals should be listed in order of their status, then time, then id
   603  	want := []entities.Proposal{
   604  		testProposals[18],
   605  		testProposals[8],
   606  		testProposals[12],
   607  	}
   608  	assert.Equal(t, want, got)
   609  	assert.Equal(t, entities.PageInfo{
   610  		HasNextPage:     true,
   611  		HasPreviousPage: false,
   612  		StartCursor:     testProposals[18].Cursor().Encode(),
   613  		EndCursor:       testProposals[12].Cursor().Encode(),
   614  	}, pageInfo)
   615  }
   616  
   617  func testProposalCursorPaginationWithFirstAndAfterNewestFirst(t *testing.T) {
   618  	ctx := context.Background()
   619  
   620  	ps := sqlstore.NewProposals(connectionSource)
   621  	first := int32(8)
   622  	after := testProposals[12].Cursor().Encode()
   623  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   624  	require.NoError(t, err)
   625  
   626  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   627  	require.NoError(t, err)
   628  	// Proposals should be listed in order of their status, then time, then id
   629  	want := []entities.Proposal{
   630  		testProposals[2],
   631  		testProposals[11],
   632  		testProposals[1],
   633  		testProposals[10],
   634  		testProposals[0],
   635  		testProposals[19],
   636  		testProposals[9],
   637  		testProposals[17],
   638  	}
   639  	assert.Equal(t, want, got)
   640  	assert.Equal(t, entities.PageInfo{
   641  		HasNextPage:     true,
   642  		HasPreviousPage: true,
   643  		StartCursor:     testProposals[2].Cursor().Encode(),
   644  		EndCursor:       testProposals[17].Cursor().Encode(),
   645  	}, pageInfo)
   646  }
   647  
   648  func testProposalCursorPaginationWithLastNewestFirst(t *testing.T) {
   649  	ctx := context.Background()
   650  
   651  	ps := sqlstore.NewProposals(connectionSource)
   652  	last := int32(3)
   653  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   654  	require.NoError(t, err)
   655  
   656  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   657  	require.NoError(t, err)
   658  	// Proposals should be listed in order of their status, then time, then id
   659  	want := []entities.Proposal{
   660  		testProposals[4],
   661  		testProposals[13],
   662  		testProposals[3],
   663  	}
   664  	assert.Equal(t, want, got)
   665  	assert.Equal(t, entities.PageInfo{
   666  		HasNextPage:     false,
   667  		HasPreviousPage: true,
   668  		StartCursor:     testProposals[4].Cursor().Encode(),
   669  		EndCursor:       testProposals[3].Cursor().Encode(),
   670  	}, pageInfo)
   671  }
   672  
   673  func testProposalCursorPaginationWithLastAndBeforeNewestFirst(t *testing.T) {
   674  	ctx := context.Background()
   675  
   676  	ps := sqlstore.NewProposals(connectionSource)
   677  	last := int32(8)
   678  	before := testProposals[16].Cursor().Encode()
   679  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   680  	require.NoError(t, err)
   681  
   682  	got, pageInfo, err := ps.Get(ctx, nil, nil, nil, pagination)
   683  	require.NoError(t, err)
   684  	// Proposals should be listed in order of their status, then time, then id
   685  	want := []entities.Proposal{
   686  		testProposals[11],
   687  		testProposals[1],
   688  		testProposals[10],
   689  		testProposals[0],
   690  		testProposals[19],
   691  		testProposals[9],
   692  		testProposals[17],
   693  		testProposals[7],
   694  	}
   695  	assert.Equal(t, want, got)
   696  	assert.Equal(t, entities.PageInfo{
   697  		HasNextPage:     true,
   698  		HasPreviousPage: true,
   699  		StartCursor:     testProposals[11].Cursor().Encode(),
   700  		EndCursor:       testProposals[7].Cursor().Encode(),
   701  	}, pageInfo)
   702  }
   703  
   704  func testProposalCursorPaginationNoPaginationByParty(t *testing.T) {
   705  	ctx := context.Background()
   706  
   707  	ps := sqlstore.NewProposals(connectionSource)
   708  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   709  	require.NoError(t, err)
   710  
   711  	partyID := proposalTestParties[0].ID.String()
   712  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   713  	require.NoError(t, err)
   714  	// Proposals should be listed in order of their status, then time, then id
   715  	want := []entities.Proposal{
   716  		testProposals[0],
   717  		testProposals[1],
   718  		testProposals[2],
   719  		testProposals[8],
   720  		testProposals[3],
   721  		testProposals[4],
   722  		testProposals[5],
   723  		testProposals[6],
   724  		testProposals[7],
   725  		testProposals[9],
   726  	}
   727  	assert.Equal(t, want, got)
   728  	assert.Equal(t, entities.PageInfo{
   729  		HasNextPage:     false,
   730  		HasPreviousPage: false,
   731  		StartCursor:     testProposals[0].Cursor().Encode(),
   732  		EndCursor:       testProposals[9].Cursor().Encode(),
   733  	}, pageInfo)
   734  }
   735  
   736  func testProposalCursorPaginationWithFirstByParty(t *testing.T) {
   737  	ctx := context.Background()
   738  
   739  	ps := sqlstore.NewProposals(connectionSource)
   740  	first := int32(3)
   741  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   742  	require.NoError(t, err)
   743  
   744  	partyID := proposalTestParties[0].ID.String()
   745  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   746  	require.NoError(t, err)
   747  	// Proposals should be listed in order of their status, then time, then id
   748  	want := []entities.Proposal{
   749  		testProposals[0],
   750  		testProposals[1],
   751  		testProposals[2],
   752  	}
   753  	assert.Equal(t, want, got)
   754  	assert.Equal(t, entities.PageInfo{
   755  		HasNextPage:     true,
   756  		HasPreviousPage: false,
   757  		StartCursor:     testProposals[0].Cursor().Encode(),
   758  		EndCursor:       testProposals[2].Cursor().Encode(),
   759  	}, pageInfo)
   760  }
   761  
   762  func testProposalCursorPaginationWithFirstAndAfterByParty(t *testing.T) {
   763  	ctx := context.Background()
   764  
   765  	ps := sqlstore.NewProposals(connectionSource)
   766  	first := int32(3)
   767  	after := testProposals[2].Cursor().Encode()
   768  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   769  	require.NoError(t, err)
   770  
   771  	partyID := proposalTestParties[0].ID.String()
   772  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   773  	require.NoError(t, err)
   774  	// Proposals should be listed in order of their status, then time, then id
   775  	want := []entities.Proposal{
   776  		testProposals[8],
   777  		testProposals[3],
   778  		testProposals[4],
   779  	}
   780  	assert.Equal(t, want, got)
   781  	assert.Equal(t, entities.PageInfo{
   782  		HasNextPage:     true,
   783  		HasPreviousPage: true,
   784  		StartCursor:     testProposals[8].Cursor().Encode(),
   785  		EndCursor:       testProposals[4].Cursor().Encode(),
   786  	}, pageInfo)
   787  }
   788  
   789  func testProposalCursorPaginationWithLastByParty(t *testing.T) {
   790  	ctx := context.Background()
   791  
   792  	ps := sqlstore.NewProposals(connectionSource)
   793  	last := int32(3)
   794  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   795  	require.NoError(t, err)
   796  
   797  	partyID := proposalTestParties[0].ID.String()
   798  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   799  	require.NoError(t, err)
   800  	// Proposals should be listed in order of their status, then time, then id
   801  	want := []entities.Proposal{
   802  		testProposals[6],
   803  		testProposals[7],
   804  		testProposals[9],
   805  	}
   806  	assert.Equal(t, want, got)
   807  	assert.Equal(t, entities.PageInfo{
   808  		HasNextPage:     false,
   809  		HasPreviousPage: true,
   810  		StartCursor:     testProposals[6].Cursor().Encode(),
   811  		EndCursor:       testProposals[9].Cursor().Encode(),
   812  	}, pageInfo)
   813  }
   814  
   815  func testProposalCursorPaginationWithLastAndBeforeByParty(t *testing.T) {
   816  	ctx := context.Background()
   817  
   818  	ps := sqlstore.NewProposals(connectionSource)
   819  	last := int32(5)
   820  	before := testProposals[6].Cursor().Encode()
   821  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   822  	require.NoError(t, err)
   823  
   824  	partyID := proposalTestParties[0].ID.String()
   825  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   826  	require.NoError(t, err)
   827  	// Proposals should be listed in order of their status, then time, then id
   828  	want := []entities.Proposal{
   829  		testProposals[2],
   830  		testProposals[8],
   831  		testProposals[3],
   832  		testProposals[4],
   833  		testProposals[5],
   834  	}
   835  	assert.Equal(t, want, got)
   836  	assert.Equal(t, entities.PageInfo{
   837  		HasNextPage:     true,
   838  		HasPreviousPage: true,
   839  		StartCursor:     testProposals[2].Cursor().Encode(),
   840  		EndCursor:       testProposals[5].Cursor().Encode(),
   841  	}, pageInfo)
   842  }
   843  
   844  func testProposalCursorPaginationNoPaginationByPartyNewestFirst(t *testing.T) {
   845  	ctx := context.Background()
   846  
   847  	ps := sqlstore.NewProposals(connectionSource)
   848  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, true)
   849  	require.NoError(t, err)
   850  
   851  	partyID := proposalTestParties[0].ID.String()
   852  
   853  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   854  	require.NoError(t, err)
   855  	// Proposals should be listed in order of their status, then time, then id
   856  	want := []entities.Proposal{
   857  		testProposals[8],
   858  		testProposals[2],
   859  		testProposals[1],
   860  		testProposals[0],
   861  		testProposals[9],
   862  		testProposals[7],
   863  		testProposals[6],
   864  		testProposals[5],
   865  		testProposals[4],
   866  		testProposals[3],
   867  	}
   868  	assert.Equal(t, want, got)
   869  	assert.Equal(t, entities.PageInfo{
   870  		HasNextPage:     false,
   871  		HasPreviousPage: false,
   872  		StartCursor:     testProposals[8].Cursor().Encode(),
   873  		EndCursor:       testProposals[3].Cursor().Encode(),
   874  	}, pageInfo)
   875  }
   876  
   877  func testProposalCursorPaginationWithFirstByPartyNewestFirst(t *testing.T) {
   878  	ctx := context.Background()
   879  
   880  	ps := sqlstore.NewProposals(connectionSource)
   881  	first := int32(3)
   882  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   883  	require.NoError(t, err)
   884  
   885  	partyID := proposalTestParties[0].ID.String()
   886  
   887  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   888  	require.NoError(t, err)
   889  	// Proposals should be listed in order of their status, then time, then id
   890  	want := []entities.Proposal{
   891  		testProposals[8],
   892  		testProposals[2],
   893  		testProposals[1],
   894  	}
   895  	assert.Equal(t, want, got)
   896  	assert.Equal(t, entities.PageInfo{
   897  		HasNextPage:     true,
   898  		HasPreviousPage: false,
   899  		StartCursor:     testProposals[8].Cursor().Encode(),
   900  		EndCursor:       testProposals[1].Cursor().Encode(),
   901  	}, pageInfo)
   902  }
   903  
   904  func testProposalCursorPaginationWithFirstAndAfterByPartyNewestFirst(t *testing.T) {
   905  	ctx := context.Background()
   906  
   907  	ps := sqlstore.NewProposals(connectionSource)
   908  	first := int32(3)
   909  	after := testProposals[1].Cursor().Encode()
   910  	pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   911  	require.NoError(t, err)
   912  
   913  	partyID := proposalTestParties[0].ID.String()
   914  
   915  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   916  	require.NoError(t, err)
   917  	// Proposals should be listed in order of their status, then time, then id
   918  	want := []entities.Proposal{
   919  		testProposals[0],
   920  		testProposals[9],
   921  		testProposals[7],
   922  	}
   923  	assert.Equal(t, want, got)
   924  	assert.Equal(t, entities.PageInfo{
   925  		HasNextPage:     true,
   926  		HasPreviousPage: true,
   927  		StartCursor:     testProposals[0].Cursor().Encode(),
   928  		EndCursor:       testProposals[7].Cursor().Encode(),
   929  	}, pageInfo)
   930  }
   931  
   932  func testProposalCursorPaginationWithLastByPartyNewestFirst(t *testing.T) {
   933  	ctx := context.Background()
   934  
   935  	ps := sqlstore.NewProposals(connectionSource)
   936  	last := int32(3)
   937  	pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   938  	require.NoError(t, err)
   939  
   940  	partyID := proposalTestParties[0].ID.String()
   941  
   942  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   943  	require.NoError(t, err)
   944  	// Proposals should be listed in order of their status, then time, then id
   945  	want := []entities.Proposal{
   946  		testProposals[5],
   947  		testProposals[4],
   948  		testProposals[3],
   949  	}
   950  	assert.Equal(t, want, got)
   951  	assert.Equal(t, entities.PageInfo{
   952  		HasNextPage:     false,
   953  		HasPreviousPage: true,
   954  		StartCursor:     testProposals[5].Cursor().Encode(),
   955  		EndCursor:       testProposals[3].Cursor().Encode(),
   956  	}, pageInfo)
   957  }
   958  
   959  func testProposalCursorPaginationWithLastAndBeforeByPartyNewestFirst(t *testing.T) {
   960  	ctx := context.Background()
   961  
   962  	ps := sqlstore.NewProposals(connectionSource)
   963  	last := int32(5)
   964  	before := testProposals[5].Cursor().Encode()
   965  	pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   966  	require.NoError(t, err)
   967  
   968  	partyID := proposalTestParties[0].ID.String()
   969  
   970  	got, pageInfo, err := ps.Get(ctx, nil, &partyID, nil, pagination)
   971  	require.NoError(t, err)
   972  	// Proposals should be listed in order of their status, then time, then id
   973  	want := []entities.Proposal{
   974  		testProposals[1],
   975  		testProposals[0],
   976  		testProposals[9],
   977  		testProposals[7],
   978  		testProposals[6],
   979  	}
   980  	assert.Equal(t, want, got)
   981  	assert.Equal(t, entities.PageInfo{
   982  		HasNextPage:     true,
   983  		HasPreviousPage: true,
   984  		StartCursor:     testProposals[1].Cursor().Encode(),
   985  		EndCursor:       testProposals[6].Cursor().Encode(),
   986  	}, pageInfo)
   987  }
   988  
   989  func testProposalCursorPaginationOpenOnly(t *testing.T) {
   990  	ctx := context.Background()
   991  
   992  	ps := sqlstore.NewProposals(connectionSource)
   993  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
   994  	require.NoError(t, err)
   995  
   996  	state := entities.ProposalStateOpen
   997  	got, pageInfo, err := ps.Get(ctx, &state, nil, nil, pagination)
   998  	require.NoError(t, err)
   999  	// Proposals should be listed in order of their status, then time, then id
  1000  	want := []entities.Proposal{
  1001  		testProposals[0],
  1002  		testProposals[10],
  1003  		testProposals[1],
  1004  		testProposals[11],
  1005  		testProposals[2],
  1006  		testProposals[12],
  1007  		testProposals[8],
  1008  		testProposals[18],
  1009  	}
  1010  	assert.Equal(t, want, got)
  1011  	assert.Equal(t, entities.PageInfo{
  1012  		HasNextPage:     false,
  1013  		HasPreviousPage: false,
  1014  		StartCursor:     testProposals[0].Cursor().Encode(),
  1015  		EndCursor:       testProposals[18].Cursor().Encode(),
  1016  	}, pageInfo)
  1017  }
  1018  
  1019  func testProposalCursorPaginationGivenState(t *testing.T) {
  1020  	ctx := context.Background()
  1021  
  1022  	ps := sqlstore.NewProposals(connectionSource)
  1023  	pagination, err := entities.NewCursorPagination(nil, nil, nil, nil, false)
  1024  	require.NoError(t, err)
  1025  
  1026  	t.Run("State is Enacted", func(t *testing.T) {
  1027  		state := entities.ProposalStateEnacted
  1028  		got, pageInfo, err := ps.Get(ctx, &state, nil, nil, pagination)
  1029  		require.NoError(t, err)
  1030  		// Proposals should be listed in order of their status, then time, then id
  1031  		want := []entities.Proposal{
  1032  			testProposals[3],
  1033  			testProposals[13],
  1034  			testProposals[6],
  1035  			testProposals[16],
  1036  			testProposals[9],
  1037  			testProposals[19],
  1038  		}
  1039  		assert.Equal(t, want, got)
  1040  		assert.Equal(t, entities.PageInfo{
  1041  			HasNextPage:     false,
  1042  			HasPreviousPage: false,
  1043  			StartCursor:     testProposals[3].Cursor().Encode(),
  1044  			EndCursor:       testProposals[19].Cursor().Encode(),
  1045  		}, pageInfo)
  1046  	})
  1047  
  1048  	t.Run("State is Passed", func(t *testing.T) {
  1049  		state := entities.ProposalStatePassed
  1050  		got, pageInfo, err := ps.Get(ctx, &state, nil, nil, pagination)
  1051  		require.NoError(t, err)
  1052  		// Proposals should be listed in order of their status, then time, then id
  1053  		want := []entities.Proposal{
  1054  			testProposals[4],
  1055  			testProposals[14],
  1056  			testProposals[5],
  1057  			testProposals[15],
  1058  		}
  1059  		assert.Equal(t, want, got)
  1060  		assert.Equal(t, entities.PageInfo{
  1061  			HasNextPage:     false,
  1062  			HasPreviousPage: false,
  1063  			StartCursor:     testProposals[4].Cursor().Encode(),
  1064  			EndCursor:       testProposals[15].Cursor().Encode(),
  1065  		}, pageInfo)
  1066  	})
  1067  }
  1068  
  1069  func createPaginationTestProposals(t *testing.T, ctx context.Context, pps *sqlstore.Proposals) ([]entities.Proposal, []entities.Party) {
  1070  	t.Helper()
  1071  	ps := sqlstore.NewParties(connectionSource)
  1072  	bs := sqlstore.NewBlocks(connectionSource)
  1073  
  1074  	testProposals := make([]entities.Proposal, 20)
  1075  
  1076  	blockTime := time.Date(2022, 7, 15, 8, 0, 0, 0, time.Local)
  1077  	block := addTestBlockForTime(t, ctx, bs, blockTime)
  1078  
  1079  	parties := []entities.Party{
  1080  		addTestParty(t, ctx, ps, block),
  1081  		addTestParty(t, ctx, ps, block),
  1082  	}
  1083  
  1084  	states := []entities.ProposalState{
  1085  		entities.ProposalStateOpen,
  1086  		entities.ProposalStateOpen,
  1087  		entities.ProposalStateOpen,
  1088  		entities.ProposalStateEnacted,
  1089  		entities.ProposalStatePassed,
  1090  		entities.ProposalStatePassed,
  1091  		entities.ProposalStateEnacted,
  1092  		entities.ProposalStateDeclined,
  1093  		entities.ProposalStateOpen,
  1094  		entities.ProposalStateEnacted,
  1095  	}
  1096  	i := 0
  1097  	for i < 10 {
  1098  		blockTime = blockTime.Add(time.Minute)
  1099  		block = addTestBlockForTime(t, ctx, bs, blockTime)
  1100  		block2 := addTestBlockForTime(t, ctx, bs, blockTime.Add(time.Second*30))
  1101  
  1102  		id1 := fmt.Sprintf("deadbeef%02d", i)
  1103  		id2 := fmt.Sprintf("deadbeef%02d", i+10)
  1104  
  1105  		ref1 := fmt.Sprintf("cafed00d%02d", i)
  1106  		ref2 := fmt.Sprintf("cafed00d%02d", i+10)
  1107  		rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: fmt.Sprintf("https://rationale1-%02d.com", i), Description: "desc"}}
  1108  		rationale2 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: fmt.Sprintf("https://rationale1-%02d.com", i+10), Description: "desc"}}
  1109  		terms1 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}}
  1110  		terms2 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewAsset{NewAsset: &vega.NewAsset{}}}}
  1111  
  1112  		testProposals[i] = addTestProposal(t, ctx, pps, id1, parties[0], ref1, block, states[i], rationale1, terms1, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
  1113  		testProposals[i+10] = addTestProposal(t, ctx, pps, id2, parties[1], ref2, block2, states[i], rationale2, terms2, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
  1114  		i++
  1115  	}
  1116  
  1117  	return testProposals, parties
  1118  }
  1119  
  1120  func cleanupTestProposals(t *testing.T) {
  1121  	t.Helper()
  1122  	// Remove the proposals, then the parties and then the blocks
  1123  	_, err := connectionSource.Exec(context.Background(), `TRUNCATE TABLE proposals`)
  1124  	require.NoError(t, err)
  1125  	_, err = connectionSource.Exec(context.Background(), `TRUNCATE TABLE parties`)
  1126  	require.NoError(t, err)
  1127  	_, err = connectionSource.Exec(context.Background(), `TRUNCATE TABLE blocks`)
  1128  	require.NoError(t, err)
  1129  }
  1130  
  1131  func TestProposeSuccessorMarket(t *testing.T) {
  1132  	ctx := tempTransaction(t)
  1133  
  1134  	partyStore := sqlstore.NewParties(connectionSource)
  1135  	propStore := sqlstore.NewProposals(connectionSource)
  1136  	blockStore := sqlstore.NewBlocks(connectionSource)
  1137  	block1 := addTestBlock(t, ctx, blockStore)
  1138  
  1139  	party1 := addTestParty(t, ctx, partyStore, block1)
  1140  	rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}
  1141  	rationale2 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl2.com", Description: "desc"}}
  1142  	terms1 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{
  1143  		Changes: &vega.NewMarketConfiguration{
  1144  			Instrument:                    nil,
  1145  			DecimalPlaces:                 0,
  1146  			Metadata:                      nil,
  1147  			PriceMonitoringParameters:     nil,
  1148  			LiquidityMonitoringParameters: nil,
  1149  			RiskParameters:                nil,
  1150  			PositionDecimalPlaces:         0,
  1151  			LinearSlippageFactor:          "",
  1152  			QuadraticSlippageFactor:       "",
  1153  			Successor:                     nil,
  1154  		},
  1155  	}}}}
  1156  	terms2 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{
  1157  		Changes: &vega.NewMarketConfiguration{
  1158  			Instrument:                    nil,
  1159  			DecimalPlaces:                 0,
  1160  			Metadata:                      nil,
  1161  			PriceMonitoringParameters:     nil,
  1162  			LiquidityMonitoringParameters: nil,
  1163  			RiskParameters:                nil,
  1164  			PositionDecimalPlaces:         0,
  1165  			LinearSlippageFactor:          "",
  1166  			QuadraticSlippageFactor:       "",
  1167  			Successor: &vega.SuccessorConfiguration{
  1168  				ParentMarketId:        "deadbeef",
  1169  				InsurancePoolFraction: "0.5",
  1170  			},
  1171  		},
  1172  	}}}}
  1173  	id1 := GenerateID()
  1174  	id2 := GenerateID()
  1175  
  1176  	reference1 := GenerateID()
  1177  	reference2 := GenerateID()
  1178  	prop1 := addTestProposal(t, ctx, propStore, id1, party1, reference1, block1, entities.ProposalStateEnacted, rationale1, terms1, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
  1179  	prop2 := addTestProposal(t, ctx, propStore, id2, party1, reference2, block1, entities.ProposalStateRejected, rationale2, terms2, entities.ProposalErrorInvalidSuccessorMarket, nil, entities.BatchProposalTerms{})
  1180  
  1181  	t.Run("GetByID", func(t *testing.T) {
  1182  		want := prop1
  1183  		got, err := propStore.GetByID(ctx, prop1.ID.String())
  1184  		require.NoError(t, err)
  1185  		assertProposalMatch(t, want, got)
  1186  
  1187  		want = prop2
  1188  		got, err = propStore.GetByID(ctx, prop2.ID.String())
  1189  		require.NoError(t, err)
  1190  		assertProposalMatch(t, want, got)
  1191  	})
  1192  }
  1193  
  1194  func getNewProposal(partyID string) *vega.Proposal {
  1195  	return &vega.Proposal{
  1196  		Id:        GenerateID(),
  1197  		Reference: GenerateID(),
  1198  		PartyId:   partyID,
  1199  		State:     vega.Proposal_STATE_OPEN,
  1200  		Timestamp: time.Now().UnixNano(),
  1201  		Rationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"},
  1202  		Terms: &vega.ProposalTerms{
  1203  			Change: &vega.ProposalTerms_NewMarket{
  1204  				NewMarket: &vega.NewMarket{
  1205  					Changes: &vega.NewMarketConfiguration{
  1206  						Instrument: &vega.InstrumentConfiguration{},
  1207  					},
  1208  				},
  1209  			},
  1210  		},
  1211  	}
  1212  }
  1213  
  1214  func getNewSpotMarketProposal(partyID string) *vega.Proposal {
  1215  	proposal := getNewProposal(partyID)
  1216  
  1217  	proposal.Terms.Change = &vega.ProposalTerms_NewSpotMarket{
  1218  		NewSpotMarket: &vega.NewSpotMarket{
  1219  			Changes: &vega.NewSpotMarketConfiguration{
  1220  				Instrument: &vega.InstrumentConfiguration{
  1221  					Product: &vega.InstrumentConfiguration_Spot{
  1222  						Spot: &vega.SpotProduct{
  1223  							BaseAsset:  "USD",
  1224  							QuoteAsset: "ETH",
  1225  						},
  1226  					},
  1227  				},
  1228  			},
  1229  		},
  1230  	}
  1231  	return proposal
  1232  }
  1233  
  1234  func getSpotMarketUpdateProposal(partyID string) *vega.Proposal {
  1235  	proposal := getNewProposal(partyID)
  1236  	proposal.Terms.Change = &vega.ProposalTerms_UpdateSpotMarket{
  1237  		UpdateSpotMarket: &vega.UpdateSpotMarket{
  1238  			MarketId: "USD/ETH",
  1239  			Changes: &vega.UpdateSpotMarketConfiguration{
  1240  				Metadata: []string{"ETH", "USD"},
  1241  				PriceMonitoringParameters: &vega.PriceMonitoringParameters{
  1242  					Triggers: []*vega.PriceMonitoringTrigger{
  1243  						{
  1244  							Horizon:          1,
  1245  							Probability:      "0.5",
  1246  							AuctionExtension: 0,
  1247  						},
  1248  					},
  1249  				},
  1250  				TargetStakeParameters: &vega.TargetStakeParameters{
  1251  					TimeWindow:    1,
  1252  					ScalingFactor: 1,
  1253  				},
  1254  				RiskParameters: &vega.UpdateSpotMarketConfiguration_Simple{
  1255  					Simple: &vega.SimpleModelParams{
  1256  						FactorLong:           1,
  1257  						FactorShort:          1,
  1258  						MaxMoveUp:            1,
  1259  						MinMoveDown:          1,
  1260  						ProbabilityOfTrading: 1,
  1261  					},
  1262  				},
  1263  				SlaParams: &vega.LiquiditySLAParameters{
  1264  					PriceRange:                  "",
  1265  					CommitmentMinTimeFraction:   "0.5",
  1266  					PerformanceHysteresisEpochs: 2,
  1267  					SlaCompetitionFactor:        "0.75",
  1268  				},
  1269  			},
  1270  		},
  1271  	}
  1272  	return proposal
  1273  }
  1274  
  1275  func getNewPerpetualMarketProposal(partyID string) *vega.Proposal {
  1276  	pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)
  1277  	proposal := getNewProposal(partyID)
  1278  
  1279  	proposal.Terms.Change = &vega.ProposalTerms_NewMarket{
  1280  		NewMarket: &vega.NewMarket{
  1281  			Changes: &vega.NewMarketConfiguration{
  1282  				Instrument: &vega.InstrumentConfiguration{
  1283  					Product: &vega.InstrumentConfiguration_Perpetual{
  1284  						Perpetual: &vega.PerpetualProduct{
  1285  							SettlementAsset:     "Ethereum/Ether",
  1286  							QuoteName:           "ETH-230929",
  1287  							MarginFundingFactor: "0.5",
  1288  							InterestRate:        "0.0125",
  1289  							ClampLowerBound:     "0.2",
  1290  							ClampUpperBound:     "0.8",
  1291  							DataSourceSpecForSettlementSchedule: &vega.DataSourceDefinition{
  1292  								SourceType: &vega.DataSourceDefinition_External{
  1293  									External: &vega.DataSourceDefinitionExternal{
  1294  										SourceType: &vega.DataSourceDefinitionExternal_Oracle{
  1295  											Oracle: &vega.DataSourceSpecConfiguration{
  1296  												Signers: []*datav1.Signer{pk.IntoProto()},
  1297  												Filters: []*datav1.Filter{
  1298  													{
  1299  														Key: &datav1.PropertyKey{
  1300  															Name: "prices.ETH.value",
  1301  															Type: datav1.PropertyKey_TYPE_INTEGER,
  1302  														},
  1303  														Conditions: []*datav1.Condition{},
  1304  													},
  1305  												},
  1306  											},
  1307  										},
  1308  									},
  1309  								},
  1310  							},
  1311  							DataSourceSpecForSettlementData: &vega.DataSourceDefinition{
  1312  								SourceType: &vega.DataSourceDefinition_Internal{
  1313  									Internal: &vega.DataSourceDefinitionInternal{
  1314  										SourceType: &vega.DataSourceDefinitionInternal_Time{
  1315  											Time: &vega.DataSourceSpecConfigurationTime{
  1316  												Conditions: []*datav1.Condition{
  1317  													{
  1318  														Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1319  														Value:    "2023-09-29T00:00:00.000000000Z",
  1320  													},
  1321  												},
  1322  											},
  1323  										},
  1324  									},
  1325  								},
  1326  							},
  1327  							DataSourceSpecBinding: &vega.DataSourceSpecToPerpetualBinding{
  1328  								SettlementDataProperty:     "prices.ETH.value",
  1329  								SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z",
  1330  							},
  1331  						},
  1332  					},
  1333  				},
  1334  			},
  1335  		},
  1336  	}
  1337  	return proposal
  1338  }
  1339  
  1340  func getPerpetualMarketUpdateProposal(partyID string) *vega.Proposal {
  1341  	pk := dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)
  1342  	proposal := getNewProposal(partyID)
  1343  
  1344  	proposal.Terms.Change = &vega.ProposalTerms_UpdateMarket{
  1345  		UpdateMarket: &vega.UpdateMarket{
  1346  			Changes: &vega.UpdateMarketConfiguration{
  1347  				Instrument: &vega.UpdateInstrumentConfiguration{
  1348  					Product: &vega.UpdateInstrumentConfiguration_Perpetual{
  1349  						Perpetual: &vega.UpdatePerpetualProduct{
  1350  							QuoteName:           "ETH-230929",
  1351  							MarginFundingFactor: "0.6",
  1352  							InterestRate:        "0.015",
  1353  							ClampLowerBound:     "0.1",
  1354  							ClampUpperBound:     "0.9",
  1355  							DataSourceSpecForSettlementSchedule: &vega.DataSourceDefinition{
  1356  								SourceType: &vega.DataSourceDefinition_External{
  1357  									External: &vega.DataSourceDefinitionExternal{
  1358  										SourceType: &vega.DataSourceDefinitionExternal_Oracle{
  1359  											Oracle: &vega.DataSourceSpecConfiguration{
  1360  												Signers: []*datav1.Signer{pk.IntoProto()},
  1361  												Filters: []*datav1.Filter{
  1362  													{
  1363  														Key: &datav1.PropertyKey{
  1364  															Name: "prices.ETH.value",
  1365  															Type: datav1.PropertyKey_TYPE_INTEGER,
  1366  														},
  1367  														Conditions: []*datav1.Condition{},
  1368  													},
  1369  												},
  1370  											},
  1371  										},
  1372  									},
  1373  								},
  1374  							},
  1375  							DataSourceSpecForSettlementData: &vega.DataSourceDefinition{
  1376  								SourceType: &vega.DataSourceDefinition_Internal{
  1377  									Internal: &vega.DataSourceDefinitionInternal{
  1378  										SourceType: &vega.DataSourceDefinitionInternal_Time{
  1379  											Time: &vega.DataSourceSpecConfigurationTime{
  1380  												Conditions: []*datav1.Condition{
  1381  													{
  1382  														Operator: datav1.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
  1383  														Value:    "2023-09-29T00:00:00.000000000Z",
  1384  													},
  1385  												},
  1386  											},
  1387  										},
  1388  									},
  1389  								},
  1390  							},
  1391  							DataSourceSpecBinding: &vega.DataSourceSpecToPerpetualBinding{
  1392  								SettlementDataProperty:     "prices.ETH.value",
  1393  								SettlementScheduleProperty: "2023-09-29T00:00:00.000000000Z",
  1394  							},
  1395  						},
  1396  					},
  1397  				},
  1398  			},
  1399  		},
  1400  	}
  1401  
  1402  	return proposal
  1403  }
  1404  
  1405  func setupProposalTest(t *testing.T) (*sqlstore.Blocks, *sqlstore.Parties, *sqlstore.Proposals) {
  1406  	t.Helper()
  1407  	partyStore := sqlstore.NewParties(connectionSource)
  1408  	propStore := sqlstore.NewProposals(connectionSource)
  1409  	blockStore := sqlstore.NewBlocks(connectionSource)
  1410  
  1411  	return blockStore, partyStore, propStore
  1412  }
  1413  
  1414  func TestSpotMarketProposal(t *testing.T) {
  1415  	t.Run("Should save and retrieve new spot market proposals to the store", testShouldSaveNewSpotMarketProposalsToStore)
  1416  	t.Run("Should save amd retrieve update spot market proposals to the store", testShouldSaveUpdateSpotMarketProposalsToStore)
  1417  }
  1418  
  1419  func testShouldSaveNewSpotMarketProposalsToStore(t *testing.T) {
  1420  	bs, pts, ps := setupProposalTest(t)
  1421  
  1422  	ctx := tempTransaction(t)
  1423  
  1424  	block1 := addTestBlock(t, ctx, bs)
  1425  	party1 := addTestParty(t, ctx, pts, block1)
  1426  
  1427  	proposalProto := getNewSpotMarketProposal(party1.ID.String())
  1428  	proposal, err := entities.ProposalFromProto(proposalProto, generateTxHash())
  1429  	require.NoError(t, err)
  1430  	t.Run("Add should save the spot market proposal to the database", func(t *testing.T) {
  1431  		err = ps.Add(ctx, proposal)
  1432  		require.NoError(t, err)
  1433  	})
  1434  
  1435  	var savedProp []entities.Proposal
  1436  	t.Run("Get should return the saved spot market proposal", func(t *testing.T) {
  1437  		savedProp, _, err = ps.Get(ctx, nil, nil, nil, entities.CursorPagination{})
  1438  		require.NoError(t, err)
  1439  		require.Len(t, savedProp, 1)
  1440  	})
  1441  
  1442  	t.Run("Proposal terms should be for a new spot market", func(t *testing.T) {
  1443  		savedToProto := savedProp[0].ToProto()
  1444  		assert.Nil(t, savedToProto.Terms.GetUpdateSpotMarket())
  1445  		assert.NotNil(t, savedToProto.Terms.GetNewSpotMarket())
  1446  	})
  1447  }
  1448  
  1449  func testShouldSaveUpdateSpotMarketProposalsToStore(t *testing.T) {
  1450  	bs, pts, ps := setupProposalTest(t)
  1451  
  1452  	ctx := tempTransaction(t)
  1453  
  1454  	block1 := addTestBlock(t, ctx, bs)
  1455  	party1 := addTestParty(t, ctx, pts, block1)
  1456  
  1457  	proposalProto := getSpotMarketUpdateProposal(party1.ID.String())
  1458  	proposal, err := entities.ProposalFromProto(proposalProto, generateTxHash())
  1459  	require.NoError(t, err)
  1460  	t.Run("Add should save the spot market proposal to the database", func(t *testing.T) {
  1461  		err = ps.Add(ctx, proposal)
  1462  		require.NoError(t, err)
  1463  	})
  1464  
  1465  	var savedProp []entities.Proposal
  1466  	t.Run("Get should return the saved spot market proposal", func(t *testing.T) {
  1467  		savedProp, _, err = ps.Get(ctx, nil, nil, nil, entities.CursorPagination{})
  1468  		require.NoError(t, err)
  1469  		require.Len(t, savedProp, 1)
  1470  	})
  1471  
  1472  	t.Run("Proposal terms should be for a new spot market", func(t *testing.T) {
  1473  		savedToProto := savedProp[0].ToProto()
  1474  		assert.Nil(t, savedToProto.Terms.GetUpdateMarket())
  1475  		assert.NotNil(t, savedToProto.Terms.GetUpdateSpotMarket())
  1476  	})
  1477  }
  1478  
  1479  func TestPerpetualMarketProposal(t *testing.T) {
  1480  	t.Run("Should save and retrieve new perpetual market proposals to the store", testShouldSaveNewPerpetualMarketProposalsToStore)
  1481  	t.Run("Should save and retrieve update perpetual market proposals to the store", testShouldUpdateSavePerpetualMarketProposalsToStore)
  1482  }
  1483  
  1484  func testShouldSaveNewPerpetualMarketProposalsToStore(t *testing.T) {
  1485  	bs, pts, ps := setupProposalTest(t)
  1486  
  1487  	ctx := tempTransaction(t)
  1488  
  1489  	block1 := addTestBlock(t, ctx, bs)
  1490  	party1 := addTestParty(t, ctx, pts, block1)
  1491  
  1492  	proposalProto := getNewPerpetualMarketProposal(party1.ID.String())
  1493  	proposal, err := entities.ProposalFromProto(proposalProto, generateTxHash())
  1494  	require.NoError(t, err)
  1495  	t.Run("Add should create a new perps market proposal  in the database", func(t *testing.T) {
  1496  		err = ps.Add(ctx, proposal)
  1497  		require.NoError(t, err)
  1498  	})
  1499  
  1500  	var savedProp []entities.Proposal
  1501  	t.Run("Get should return the perps market proposal from the database", func(t *testing.T) {
  1502  		savedProp, _, err = ps.Get(ctx, nil, nil, nil, entities.CursorPagination{})
  1503  		require.NoError(t, err)
  1504  		require.Len(t, savedProp, 1)
  1505  	})
  1506  
  1507  	t.Run("The saved proposal's instrument should be a perp and not a future or spot", func(t *testing.T) {
  1508  		savedToProto := savedProp[0].ToProto()
  1509  		assert.Nil(t, savedToProto.Terms.GetNewSpotMarket())
  1510  		assert.NotNil(t, savedToProto.Terms.GetNewMarket())
  1511  		future := savedToProto.Terms.GetNewMarket().GetChanges().GetInstrument().GetFuture()
  1512  		assert.Nil(t, future)
  1513  		perps := savedToProto.Terms.GetNewMarket().GetChanges().GetInstrument().GetPerpetual()
  1514  		assert.NotNil(t, perps)
  1515  	})
  1516  }
  1517  
  1518  func testShouldUpdateSavePerpetualMarketProposalsToStore(t *testing.T) {
  1519  	bs, pts, ps := setupProposalTest(t)
  1520  
  1521  	ctx := tempTransaction(t)
  1522  
  1523  	block1 := addTestBlock(t, ctx, bs)
  1524  	party1 := addTestParty(t, ctx, pts, block1)
  1525  
  1526  	proposalProto := getPerpetualMarketUpdateProposal(party1.ID.String())
  1527  	proposal, err := entities.ProposalFromProto(proposalProto, generateTxHash())
  1528  	require.NoError(t, err)
  1529  	t.Run("Add should create a update perps market proposal  in the database", func(t *testing.T) {
  1530  		err = ps.Add(ctx, proposal)
  1531  		require.NoError(t, err)
  1532  	})
  1533  
  1534  	var savedProp []entities.Proposal
  1535  	t.Run("Get should return the perps market proposal from the database", func(t *testing.T) {
  1536  		savedProp, _, err = ps.Get(ctx, nil, nil, nil, entities.CursorPagination{})
  1537  		require.NoError(t, err)
  1538  		require.Len(t, savedProp, 1)
  1539  	})
  1540  
  1541  	t.Run("The saved proposal's instrument should be a perp and not a future or spot", func(t *testing.T) {
  1542  		savedToProto := savedProp[0].ToProto()
  1543  		assert.Nil(t, savedToProto.Terms.GetNewSpotMarket())
  1544  		assert.NotNil(t, savedToProto.Terms.GetUpdateMarket())
  1545  		future := savedToProto.Terms.GetUpdateMarket().GetChanges().GetInstrument().GetFuture()
  1546  		assert.Nil(t, future)
  1547  		perps := savedToProto.Terms.GetUpdateMarket().GetChanges().GetInstrument().GetPerpetual()
  1548  		assert.NotNil(t, perps)
  1549  	})
  1550  }
  1551  
  1552  func TestProposalEnums(t *testing.T) {
  1553  	t.Run("Should save and retrieve proposals with all possible errors", testProposalError)
  1554  	t.Run("Should save and retrieve proposals with all possible states", testProposalState)
  1555  }
  1556  
  1557  func testProposalError(t *testing.T) {
  1558  	var proposalError vega.ProposalError
  1559  	errs := getEnums(t, proposalError)
  1560  	assert.Len(t, errs, 57)
  1561  
  1562  	for e, err := range errs {
  1563  		t.Run(err, func(t *testing.T) {
  1564  			ctx := tempTransaction(t)
  1565  
  1566  			partyStore := sqlstore.NewParties(connectionSource)
  1567  			propStore := sqlstore.NewProposals(connectionSource)
  1568  			blockStore := sqlstore.NewBlocks(connectionSource)
  1569  			block1 := addTestBlock(t, ctx, blockStore)
  1570  
  1571  			party1 := addTestParty(t, ctx, partyStore, block1)
  1572  			rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}
  1573  			terms1 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}}
  1574  			id1 := GenerateID()
  1575  
  1576  			reference1 := GenerateID()
  1577  			prop1 := addTestProposal(t, ctx, propStore, id1, party1, reference1, block1, entities.ProposalStateEnacted, rationale1, terms1, entities.ProposalError(e), nil, entities.BatchProposalTerms{})
  1578  
  1579  			prop1ID := prop1.ID.String()
  1580  
  1581  			expected := prop1
  1582  			actual, err := propStore.GetByID(ctx, prop1ID)
  1583  			require.NoError(t, err)
  1584  			assertProposalMatch(t, expected, actual)
  1585  		})
  1586  	}
  1587  }
  1588  
  1589  func testProposalState(t *testing.T) {
  1590  	var proposalState vega.Proposal_State
  1591  	errs := getEnums(t, proposalState)
  1592  	assert.Len(t, errs, 8)
  1593  
  1594  	for s, state := range errs {
  1595  		t.Run(state, func(t *testing.T) {
  1596  			ctx := tempTransaction(t)
  1597  
  1598  			partyStore := sqlstore.NewParties(connectionSource)
  1599  			propStore := sqlstore.NewProposals(connectionSource)
  1600  			blockStore := sqlstore.NewBlocks(connectionSource)
  1601  			block1 := addTestBlock(t, ctx, blockStore)
  1602  
  1603  			party1 := addTestParty(t, ctx, partyStore, block1)
  1604  			rationale1 := entities.ProposalRationale{ProposalRationale: &vega.ProposalRationale{Title: "myurl1.com", Description: "desc"}}
  1605  			terms1 := entities.ProposalTerms{ProposalTerms: &vega.ProposalTerms{Change: &vega.ProposalTerms_NewMarket{NewMarket: &vega.NewMarket{}}}}
  1606  			id1 := GenerateID()
  1607  
  1608  			reference1 := GenerateID()
  1609  			prop1 := addTestProposal(t, ctx, propStore, id1, party1, reference1, block1, entities.ProposalState(s), rationale1, terms1, entities.ProposalErrorUnspecified, nil, entities.BatchProposalTerms{})
  1610  
  1611  			prop1ID := prop1.ID.String()
  1612  
  1613  			expected := prop1
  1614  			actual, err := propStore.GetByID(ctx, prop1ID)
  1615  			require.NoError(t, err)
  1616  			assertProposalMatch(t, expected, actual)
  1617  		})
  1618  	}
  1619  }