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 }