code.vegaprotocol.io/vega@v0.79.0/commands/proposal_submission_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 commands_test
    17  
    18  import (
    19  	"errors"
    20  	"testing"
    21  
    22  	"code.vegaprotocol.io/vega/commands"
    23  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    24  	"code.vegaprotocol.io/vega/libs/test"
    25  	types "code.vegaprotocol.io/vega/protos/vega"
    26  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  )
    30  
    31  func TestCheckProposalSubmission(t *testing.T) {
    32  	t.Run("Submitting a nil command fails", testNilProposalSubmissionFails)
    33  	t.Run("Submitting a proposal change without change fails", testProposalSubmissionWithoutChangeFails)
    34  	t.Run("Submitting a proposal without terms fails", testProposalSubmissionWithoutTermsFails)
    35  	t.Run("Submitting a proposal with non-positive closing timestamp fails", testProposalSubmissionWithNonPositiveClosingTimestampFails)
    36  	t.Run("Submitting a proposal with positive closing timestamp succeeds", testProposalSubmissionWithPositiveClosingTimestampSucceeds)
    37  	t.Run("Submitting a proposal with non-positive enactment timestamp fails", testProposalSubmissionWithNonPositiveEnactmentTimestampFails)
    38  	t.Run("Submitting a proposal with positive enactment timestamp succeeds", testProposalSubmissionWithPositiveEnactmentTimestampSucceeds)
    39  	t.Run("Submitting a proposal with negative validation timestamp fails", testProposalSubmissionWithNegativeValidationTimestampFails)
    40  	t.Run("Submitting a proposal with positive validation timestamp succeeds", testProposalSubmissionWithPositiveValidationTimestampSucceeds)
    41  	t.Run("Submitting a proposal with closing timestamp after enactment timestamp fails", testProposalSubmissionWithClosingTimestampAfterEnactmentTimestampFails)
    42  	t.Run("Submitting a proposal with closing timestamp before enactment timestamp succeeds", testProposalSubmissionWithClosingTimestampBeforeEnactmentTimestampSucceeds)
    43  	t.Run("Submitting a proposal with closing timestamp at enactment timestamp succeeds", testProposalSubmissionWithClosingTimestampAtEnactmentTimestampSucceeds)
    44  	t.Run("Submitting a proposal with validation timestamp after closing timestamp fails", testProposalSubmissionWithValidationTimestampAfterClosingTimestampFails)
    45  	t.Run("Submitting a proposal with validation timestamp at closing timestamp succeeds", testProposalSubmissionWithValidationTimestampAtClosingTimestampFails)
    46  	t.Run("Submitting a proposal with validation timestamp before closing timestamp fails", testProposalSubmissionWithValidationTimestampBeforeClosingTimestampSucceeds)
    47  	t.Run("Submitting a proposal without rational fails", testProposalSubmissionWithoutRationalFails)
    48  	t.Run("Submitting a proposal with rational succeeds", testProposalSubmissionWithRationalSucceeds)
    49  	t.Run("Submitting a proposal with rational description succeeds", testProposalSubmissionWithRationalDescriptionSucceeds)
    50  	t.Run("Submitting a proposal with incorrect rational description fails", testProposalSubmissionWithIncorrectRationalDescriptionFails)
    51  	t.Run("Submitting a proposal with rational URL and hash succeeds", testProposalSubmissionWithRationalDescriptionAndTitleSucceeds)
    52  }
    53  
    54  func testNilProposalSubmissionFails(t *testing.T) {
    55  	err := checkProposalSubmission(nil)
    56  
    57  	assert.Contains(t, err.Get("proposal_submission"), commands.ErrIsRequired)
    58  }
    59  
    60  func testProposalSubmissionWithoutTermsFails(t *testing.T) {
    61  	err := checkProposalSubmission(&commandspb.ProposalSubmission{})
    62  
    63  	assert.Contains(t, err.Get("proposal_submission.terms"), commands.ErrIsRequired)
    64  }
    65  
    66  func testProposalSubmissionWithoutChangeFails(t *testing.T) {
    67  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
    68  		Terms: &types.ProposalTerms{},
    69  	})
    70  
    71  	assert.Contains(t, err.Get("proposal_submission.terms.change"), commands.ErrIsRequired)
    72  }
    73  
    74  func testProposalSubmissionWithNonPositiveClosingTimestampFails(t *testing.T) {
    75  	testCases := []struct {
    76  		msg   string
    77  		value int64
    78  	}{
    79  		{
    80  			msg:   "with 0 as closing timestamp",
    81  			value: 0,
    82  		}, {
    83  			msg:   "with negative closing timestamp",
    84  			value: test.RandomNegativeI64(),
    85  		},
    86  	}
    87  	for _, tc := range testCases {
    88  		t.Run(tc.msg, func(t *testing.T) {
    89  			err := checkProposalSubmission(&commandspb.ProposalSubmission{
    90  				Terms: &types.ProposalTerms{
    91  					ClosingTimestamp: tc.value,
    92  				},
    93  			})
    94  
    95  			assert.Contains(t, err.Get("proposal_submission.terms.closing_timestamp"), commands.ErrMustBePositive)
    96  		})
    97  	}
    98  }
    99  
   100  func testProposalSubmissionWithPositiveClosingTimestampSucceeds(t *testing.T) {
   101  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   102  		Terms: &types.ProposalTerms{
   103  			ClosingTimestamp: test.RandomPositiveI64(),
   104  		},
   105  	})
   106  
   107  	assert.NotContains(t, err.Get("proposal_submission.terms.closing_timestamp"), commands.ErrMustBePositive)
   108  }
   109  
   110  func testProposalSubmissionWithNonPositiveEnactmentTimestampFails(t *testing.T) {
   111  	testCases := []struct {
   112  		msg   string
   113  		value int64
   114  	}{
   115  		{
   116  			msg:   "with 0 as closing timestamp",
   117  			value: 0,
   118  		}, {
   119  			msg:   "with negative closing timestamp",
   120  			value: test.RandomNegativeI64(),
   121  		},
   122  	}
   123  	for _, tc := range testCases {
   124  		t.Run(tc.msg, func(t *testing.T) {
   125  			err := checkProposalSubmission(&commandspb.ProposalSubmission{
   126  				Terms: &types.ProposalTerms{
   127  					EnactmentTimestamp: tc.value,
   128  				},
   129  			})
   130  
   131  			assert.Contains(t, err.Get("proposal_submission.terms.enactment_timestamp"), commands.ErrMustBePositive)
   132  		})
   133  	}
   134  }
   135  
   136  func testProposalSubmissionWithPositiveEnactmentTimestampSucceeds(t *testing.T) {
   137  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   138  		Terms: &types.ProposalTerms{
   139  			EnactmentTimestamp: test.RandomPositiveI64(),
   140  		},
   141  	})
   142  
   143  	assert.NotContains(t, err.Get("proposal_submission.terms.enactment_timestamp"), commands.ErrMustBePositive)
   144  }
   145  
   146  func testProposalSubmissionWithNegativeValidationTimestampFails(t *testing.T) {
   147  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   148  		Terms: &types.ProposalTerms{
   149  			ValidationTimestamp: test.RandomNegativeI64(),
   150  		},
   151  	})
   152  
   153  	assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"), commands.ErrMustBePositiveOrZero)
   154  }
   155  
   156  func testProposalSubmissionWithPositiveValidationTimestampSucceeds(t *testing.T) {
   157  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   158  		Terms: &types.ProposalTerms{
   159  			ValidationTimestamp: test.RandomPositiveI64(),
   160  		},
   161  	})
   162  
   163  	assert.NotContains(t, err.Get("proposal_submission.terms.validation_timestamp"), commands.ErrIsRequired)
   164  }
   165  
   166  func testProposalSubmissionWithClosingTimestampAfterEnactmentTimestampFails(t *testing.T) {
   167  	closingTime := test.RandomPositiveI64()
   168  	enactmentTime := test.RandomPositiveI64Before(closingTime)
   169  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   170  		Terms: &types.ProposalTerms{
   171  			ClosingTimestamp:   closingTime,
   172  			EnactmentTimestamp: enactmentTime,
   173  		},
   174  	})
   175  
   176  	assert.Contains(t, err.Get("proposal_submission.terms.closing_timestamp"),
   177  		errors.New("cannot be after enactment time"),
   178  	)
   179  }
   180  
   181  func testProposalSubmissionWithClosingTimestampBeforeEnactmentTimestampSucceeds(t *testing.T) {
   182  	enactmentTime := test.RandomPositiveI64()
   183  	closingTime := test.RandomPositiveI64Before(enactmentTime)
   184  
   185  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   186  		Terms: &types.ProposalTerms{
   187  			ClosingTimestamp:   closingTime,
   188  			EnactmentTimestamp: enactmentTime,
   189  		},
   190  	})
   191  
   192  	assert.NotContains(t, err.Get("proposal_submission.terms.closing_timestamp"),
   193  		errors.New("cannot be after enactment time"),
   194  	)
   195  }
   196  
   197  func testProposalSubmissionWithClosingTimestampAtEnactmentTimestampSucceeds(t *testing.T) {
   198  	enactmentTime := test.RandomPositiveI64()
   199  
   200  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   201  		Terms: &types.ProposalTerms{
   202  			ClosingTimestamp:   enactmentTime,
   203  			EnactmentTimestamp: enactmentTime,
   204  		},
   205  	})
   206  
   207  	assert.NotContains(t, err.Get("proposal_submission.terms.closing_timestamp"),
   208  		errors.New("cannot be after enactment time"),
   209  	)
   210  }
   211  
   212  func testProposalSubmissionWithValidationTimestampAfterClosingTimestampFails(t *testing.T) {
   213  	validationTime := test.RandomPositiveI64()
   214  	closingTime := test.RandomPositiveI64Before(validationTime)
   215  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   216  		Terms: &types.ProposalTerms{
   217  			ClosingTimestamp:    closingTime,
   218  			ValidationTimestamp: validationTime,
   219  		},
   220  	})
   221  
   222  	assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"),
   223  		errors.New("cannot be after or equal to closing time"),
   224  	)
   225  }
   226  
   227  func testProposalSubmissionWithValidationTimestampAtClosingTimestampFails(t *testing.T) {
   228  	validationTime := test.RandomPositiveI64()
   229  
   230  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   231  		Terms: &types.ProposalTerms{
   232  			ClosingTimestamp:    validationTime,
   233  			ValidationTimestamp: validationTime,
   234  		},
   235  	})
   236  
   237  	assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"),
   238  		errors.New("cannot be after or equal to closing time"),
   239  	)
   240  }
   241  
   242  func testProposalSubmissionWithValidationTimestampBeforeClosingTimestampSucceeds(t *testing.T) {
   243  	closingTime := test.RandomPositiveI64()
   244  	validationTime := test.RandomPositiveI64Before(closingTime)
   245  
   246  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   247  		Terms: &types.ProposalTerms{
   248  			ClosingTimestamp:    closingTime,
   249  			ValidationTimestamp: validationTime,
   250  		},
   251  	})
   252  
   253  	assert.NotContains(t, err.Get("proposal_submission.terms.validation_timestamp"),
   254  		errors.New("cannot be after or equal to closing time"),
   255  	)
   256  }
   257  
   258  func testProposalSubmissionWithoutRationalFails(t *testing.T) {
   259  	err := checkProposalSubmission(&commandspb.ProposalSubmission{})
   260  
   261  	assert.Contains(t, err.Get("proposal_submission.rationale"), commands.ErrIsRequired)
   262  }
   263  
   264  func testProposalSubmissionWithRationalSucceeds(t *testing.T) {
   265  	err := checkProposalSubmission(&commandspb.ProposalSubmission{
   266  		Rationale: &types.ProposalRationale{},
   267  	})
   268  
   269  	assert.Empty(t, err.Get("proposal_submission.rationale"))
   270  }
   271  
   272  func testProposalSubmissionWithRationalDescriptionSucceeds(t *testing.T) {
   273  	tcs := []struct {
   274  		name        string
   275  		description string
   276  	}{
   277  		{
   278  			name:        "with description of 10 characters",
   279  			description: vgrand.RandomStr(10),
   280  		}, {
   281  			name:        "with description of 1024 characters",
   282  			description: vgrand.RandomStr(1024),
   283  		},
   284  	}
   285  
   286  	for _, tc := range tcs {
   287  		t.Run(tc.name, func(tt *testing.T) {
   288  			err := checkProposalSubmission(&commandspb.ProposalSubmission{
   289  				Rationale: &types.ProposalRationale{
   290  					Description: tc.description,
   291  				},
   292  			})
   293  
   294  			assert.Empty(tt, err.Get("proposal_submission.rationale.description"))
   295  		})
   296  	}
   297  }
   298  
   299  func testProposalSubmissionWithIncorrectRationalDescriptionFails(t *testing.T) {
   300  	tcs := []struct {
   301  		name        string
   302  		description string
   303  		expectedErr error
   304  	}{
   305  		{
   306  			name:        "with empty description",
   307  			description: "",
   308  			expectedErr: commands.ErrIsRequired,
   309  		}, {
   310  			name:        "with blank description",
   311  			description: "     ",
   312  			expectedErr: commands.ErrIsRequired,
   313  		}, {
   314  			name:        "with description > 1024",
   315  			description: vgrand.RandomStr(20420),
   316  			expectedErr: commands.ErrMustNotExceed20000Chars,
   317  		},
   318  	}
   319  
   320  	for _, tc := range tcs {
   321  		t.Run(tc.name, func(tt *testing.T) {
   322  			err := checkProposalSubmission(&commandspb.ProposalSubmission{
   323  				Rationale: &types.ProposalRationale{
   324  					Description: tc.description,
   325  				},
   326  			})
   327  
   328  			assert.Contains(tt, err.Get("proposal_submission.rationale.description"), tc.expectedErr)
   329  		})
   330  	}
   331  }
   332  
   333  func testProposalSubmissionWithRationalDescriptionAndTitleSucceeds(t *testing.T) {
   334  	tcs := []struct {
   335  		name       string
   336  		shouldErr  bool
   337  		submission *commandspb.ProposalSubmission
   338  	}{
   339  		{
   340  			name: "NewMarket with rational Title and Description",
   341  			submission: &commandspb.ProposalSubmission{
   342  				Terms: &types.ProposalTerms{
   343  					Change: &types.ProposalTerms_NewMarket{},
   344  				},
   345  				Rationale: &types.ProposalRationale{
   346  					Title:       vgrand.RandomStr(10),
   347  					Description: vgrand.RandomStr(10),
   348  				},
   349  			},
   350  		}, {
   351  			name:      "NewMarket without rational Title and Description",
   352  			shouldErr: true,
   353  			submission: &commandspb.ProposalSubmission{
   354  				Terms: &types.ProposalTerms{
   355  					Change: &types.ProposalTerms_NewMarket{},
   356  				},
   357  				Rationale: &types.ProposalRationale{},
   358  			},
   359  		}, {
   360  			name: "with UpdateMarket with rational Title and Description",
   361  			submission: &commandspb.ProposalSubmission{
   362  				Terms: &types.ProposalTerms{
   363  					Change: &types.ProposalTerms_UpdateMarket{},
   364  				},
   365  				Rationale: &types.ProposalRationale{
   366  					Title:       vgrand.RandomStr(10),
   367  					Description: vgrand.RandomStr(10),
   368  				},
   369  			},
   370  		}, {
   371  			name:      "with UpdateMarket without rational Title and Description",
   372  			shouldErr: true,
   373  			submission: &commandspb.ProposalSubmission{
   374  				Terms: &types.ProposalTerms{
   375  					Change: &types.ProposalTerms_UpdateMarket{},
   376  				},
   377  				Rationale: &types.ProposalRationale{},
   378  			},
   379  		}, {
   380  			name: "with NewAsset with rational Title and Description",
   381  			submission: &commandspb.ProposalSubmission{
   382  				Terms: &types.ProposalTerms{
   383  					Change: &types.ProposalTerms_NewAsset{},
   384  				},
   385  				Rationale: &types.ProposalRationale{
   386  					Title:       vgrand.RandomStr(10),
   387  					Description: vgrand.RandomStr(10),
   388  				},
   389  			},
   390  		}, {
   391  			name:      "with NewAsset without rational Title and Description",
   392  			shouldErr: true,
   393  			submission: &commandspb.ProposalSubmission{
   394  				Terms: &types.ProposalTerms{
   395  					Change: &types.ProposalTerms_NewAsset{},
   396  				},
   397  				Rationale: &types.ProposalRationale{},
   398  			},
   399  		}, {
   400  			name: "with UpdateNetworkParameter with rational Title and Description",
   401  			submission: &commandspb.ProposalSubmission{
   402  				Terms: &types.ProposalTerms{
   403  					Change: &types.ProposalTerms_UpdateNetworkParameter{},
   404  				},
   405  				Rationale: &types.ProposalRationale{
   406  					Title:       vgrand.RandomStr(10),
   407  					Description: vgrand.RandomStr(10),
   408  				},
   409  			},
   410  		}, {
   411  			name:      "with UpdateNetworkParameter without rational Title and Description",
   412  			shouldErr: true,
   413  			submission: &commandspb.ProposalSubmission{
   414  				Terms: &types.ProposalTerms{
   415  					Change: &types.ProposalTerms_UpdateNetworkParameter{},
   416  				},
   417  				Rationale: &types.ProposalRationale{},
   418  			},
   419  		}, {
   420  			name: "with NewFreeform with rational Title and Description",
   421  			submission: &commandspb.ProposalSubmission{
   422  				Terms: &types.ProposalTerms{
   423  					Change: &types.ProposalTerms_NewFreeform{},
   424  				},
   425  				Rationale: &types.ProposalRationale{
   426  					Title:       vgrand.RandomStr(10),
   427  					Description: vgrand.RandomStr(10),
   428  				},
   429  			},
   430  		},
   431  	}
   432  
   433  	for _, tc := range tcs {
   434  		t.Run(tc.name, func(tt *testing.T) {
   435  			err := checkProposalSubmission(tc.submission)
   436  			if !tc.shouldErr {
   437  				assert.Empty(tt, err.Get("proposal_submission.rationale.title"), tc.name)
   438  				assert.Empty(tt, err.Get("proposal_submission.rationale.description"), tc.name)
   439  			} else {
   440  				assert.Contains(tt, err.Get("proposal_submission.rationale.title"), commands.ErrIsRequired, tc.name)
   441  				assert.Contains(tt, err.Get("proposal_submission.rationale.description"), commands.ErrIsRequired, tc.name)
   442  			}
   443  		})
   444  	}
   445  }
   446  
   447  func checkProposalSubmission(cmd *commandspb.ProposalSubmission) commands.Errors {
   448  	err := commands.CheckProposalSubmission(cmd)
   449  
   450  	var e commands.Errors
   451  	if ok := errors.As(err, &e); !ok {
   452  		return commands.NewErrors()
   453  	}
   454  
   455  	return e
   456  }