code.vegaprotocol.io/vega@v0.79.0/commands/proposal_submission_new_asset_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 "code.vegaprotocol.io/vega/libs/test" 24 types "code.vegaprotocol.io/vega/protos/vega" 25 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 26 27 "github.com/stretchr/testify/assert" 28 ) 29 30 func TestCheckProposalSubmissionForNewAsset(t *testing.T) { 31 t.Run("Submitting an asset change without new asset fails", TestNewAssetChangeSubmissionWithoutNewsAssetFails) 32 t.Run("Submitting an asset change without changes fails", TestNewAssetChangeSubmissionWithoutChangesFails) 33 t.Run("Submitting an asset change without source fails", TestNewAssetChangeSubmissionWithoutSourceFails) 34 t.Run("Submitting an asset change without name fails", testNewAssetChangeSubmissionWithoutNameFails) 35 t.Run("Submitting an asset change with name succeeds", testNewAssetChangeSubmissionWithNameSucceeds) 36 t.Run("Submitting an asset change without symbol fails", testNewAssetChangeSubmissionWithoutSymbolFails) 37 t.Run("Submitting an asset change with symbol succeeds", testNewAssetChangeSubmissionWithSymbolSucceeds) 38 t.Run("Submitting an asset change without decimal fails", testNewAssetChangeSubmissionWithoutDecimalsFails) 39 t.Run("Submitting an asset change with decimal succeeds", testNewAssetChangeSubmissionWithDecimalsSucceeds) 40 t.Run("Submitting an built-in asset change without built-in asset fails", testNewAssetChangeSubmissionWithoutBuiltInAssetFails) 41 t.Run("Submitting an built-in asset change without max faucet amount fails", testNewBuiltInAssetChangeSubmissionWithoutMaxFaucetAmountMintFails) 42 t.Run("Submitting an built-in asset change with max faucet amount succeeds", testNewBuiltInAssetChangeSubmissionWithMaxFaucetAmountMintSucceeds) 43 t.Run("Submitting an built-in asset change with not-a-number max faucet amount fails", testNewBuiltInAssetChangeSubmissionWithNaNMaxFaucetAmountMintFails) 44 t.Run("Submitting an ERC20 asset change without ERC20 asset fails", testNewERC20AssetChangeSubmissionWithoutErc20AssetFails) 45 t.Run("Submitting an ERC20 asset change without chain id fails", testNewERC20AssetChangeSubmissionWithoutChainIDFails) 46 t.Run("Submitting an ERC20 asset change without contract address fails", testNewERC20AssetChangeSubmissionWithoutContractAddressFails) 47 t.Run("Submitting an ERC20 asset change with contract address succeeds", testNewERC20AssetChangeSubmissionWithContractAddressSucceeds) 48 t.Run("Submitting an ERC20 asset change with invalid lifetime limit fails", testNewERC20AssetChangeSubmissionWithInvalidLifetimeLimitFails) 49 t.Run("Submitting an ERC20 asset change with valid lifetime limit succeeds", testNewERC20AssetChangeSubmissionWithValidLifetimeLimitSucceeds) 50 t.Run("Submitting an ERC20 asset change with invalid withdrawal threshold fails", testNewERC20AssetChangeSubmissionWithInvalidWithdrawalThresholdFails) 51 t.Run("Submitting an ERC20 asset change with valid withdrawal threshold succeeds", testNewERC20AssetChangeSubmissionWithValidWithdrawalThresholdSucceeds) 52 t.Run("Submitting an ERC20 asset change without validation timestamp fails", testNewAssetERC20ChangeSubmissionMissingValidationTimestamp) 53 t.Run("Submitting an ERC20 asset change with validation timestamp succeed", testNewAssetERC20ChangeSubmissionWithValidationTimestampSucceeds) 54 t.Run("Submitting an ERC20 asset change with validation after closing timestamp fails", testNewAssetERC20ChangeSubmissionValidationAfterClosingTimestampsFails) 55 t.Run("Submitting an ERC20 asset change other proposals should omit validation timestamp", testNewAssetERC20ChangeOtherProposalShouldOmitValidationTimestamp) 56 t.Run("Submitting an ERC20 asset change with invalid quantum fails", testNewERC20AssetChangeSubmissionWithInvalidQuantumFails) 57 t.Run("Submitting an ERC20 asset change with valid quantum succeeds", testNewERC20AssetChangeSubmissionWithValidQuantumSucceeds) 58 } 59 60 func testNewAssetERC20ChangeOtherProposalShouldOmitValidationTimestamp(t *testing.T) { 61 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 62 Terms: &types.ProposalTerms{ 63 ValidationTimestamp: 10, 64 ClosingTimestamp: 15, 65 EnactmentTimestamp: 20, 66 Change: &types.ProposalTerms_NewMarket{}, 67 }, 68 }) 69 70 assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"), commands.ErrIsNotSupported) 71 72 err = checkProposalSubmission(&commandspb.ProposalSubmission{ 73 Terms: &types.ProposalTerms{ 74 ClosingTimestamp: 10, 75 EnactmentTimestamp: 20, 76 Change: &types.ProposalTerms_NewMarket{}, 77 }, 78 }) 79 80 assert.Empty(t, err.Get("proposal_submission.terms.validation_timestamp")) 81 } 82 83 func testNewAssetERC20ChangeSubmissionWithValidationTimestampSucceeds(t *testing.T) { 84 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 85 Terms: &types.ProposalTerms{ 86 ValidationTimestamp: 10, 87 ClosingTimestamp: 20, 88 EnactmentTimestamp: 30, 89 Change: &types.ProposalTerms_NewAsset{}, 90 }, 91 }) 92 93 assert.Empty(t, err.Get("proposal_submission.terms.validation_timestamp")) 94 } 95 96 func testNewAssetERC20ChangeSubmissionValidationAfterClosingTimestampsFails(t *testing.T) { 97 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 98 Terms: &types.ProposalTerms{ 99 ValidationTimestamp: 10, 100 ClosingTimestamp: 5, 101 EnactmentTimestamp: 30, 102 Change: &types.ProposalTerms_NewAsset{}, 103 }, 104 }) 105 106 assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"), errors.New("cannot be after closing time")) 107 } 108 109 func testNewAssetERC20ChangeSubmissionMissingValidationTimestamp(t *testing.T) { 110 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 111 Terms: &types.ProposalTerms{ 112 Change: &types.ProposalTerms_NewAsset{}, 113 }, 114 }) 115 116 assert.Contains(t, err.Get("proposal_submission.terms.validation_timestamp"), commands.ErrMustBePositive) 117 } 118 119 func TestNewAssetChangeSubmissionWithoutNewsAssetFails(t *testing.T) { 120 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 121 Terms: &types.ProposalTerms{ 122 Change: &types.ProposalTerms_NewAsset{}, 123 }, 124 }) 125 126 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset"), commands.ErrIsRequired) 127 } 128 129 func TestNewAssetChangeSubmissionWithoutChangesFails(t *testing.T) { 130 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 131 Terms: &types.ProposalTerms{ 132 Change: &types.ProposalTerms_NewAsset{ 133 NewAsset: &types.NewAsset{}, 134 }, 135 }, 136 }) 137 138 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes"), commands.ErrIsRequired) 139 } 140 141 func TestNewAssetChangeSubmissionWithoutSourceFails(t *testing.T) { 142 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 143 Terms: &types.ProposalTerms{ 144 Change: &types.ProposalTerms_NewAsset{ 145 NewAsset: &types.NewAsset{ 146 Changes: &types.AssetDetails{}, 147 }, 148 }, 149 }, 150 }) 151 152 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source"), commands.ErrIsRequired) 153 } 154 155 func testNewAssetChangeSubmissionWithoutNameFails(t *testing.T) { 156 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 157 Terms: &types.ProposalTerms{ 158 Change: &types.ProposalTerms_NewAsset{ 159 NewAsset: &types.NewAsset{ 160 Changes: &types.AssetDetails{ 161 Name: "", 162 }, 163 }, 164 }, 165 }, 166 }) 167 168 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.name"), commands.ErrIsRequired) 169 } 170 171 func testNewAssetChangeSubmissionWithNameSucceeds(t *testing.T) { 172 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 173 Terms: &types.ProposalTerms{ 174 Change: &types.ProposalTerms_NewAsset{ 175 NewAsset: &types.NewAsset{ 176 Changes: &types.AssetDetails{ 177 Name: "My built-in asset", 178 }, 179 }, 180 }, 181 }, 182 }) 183 184 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.name")) 185 } 186 187 func testNewAssetChangeSubmissionWithoutSymbolFails(t *testing.T) { 188 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 189 Terms: &types.ProposalTerms{ 190 Change: &types.ProposalTerms_NewAsset{ 191 NewAsset: &types.NewAsset{ 192 Changes: &types.AssetDetails{ 193 Symbol: "", 194 }, 195 }, 196 }, 197 }, 198 }) 199 200 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.symbol"), commands.ErrIsRequired) 201 } 202 203 func testNewAssetChangeSubmissionWithSymbolSucceeds(t *testing.T) { 204 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 205 Terms: &types.ProposalTerms{ 206 Change: &types.ProposalTerms_NewAsset{ 207 NewAsset: &types.NewAsset{ 208 Changes: &types.AssetDetails{ 209 Symbol: "My symbol", 210 }, 211 }, 212 }, 213 }, 214 }) 215 216 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.symbol"), commands.ErrIsRequired) 217 } 218 219 func testNewAssetChangeSubmissionWithoutDecimalsFails(t *testing.T) { 220 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 221 Terms: &types.ProposalTerms{ 222 Change: &types.ProposalTerms_NewAsset{ 223 NewAsset: &types.NewAsset{ 224 Changes: &types.AssetDetails{ 225 Decimals: 0, 226 }, 227 }, 228 }, 229 }, 230 }) 231 232 assert.NotContains(t, err.Get("proposal_submission.terms.change.new_asset.changes.decimals"), commands.ErrIsRequired) 233 } 234 235 func testNewAssetChangeSubmissionWithDecimalsSucceeds(t *testing.T) { 236 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 237 Terms: &types.ProposalTerms{ 238 Change: &types.ProposalTerms_NewAsset{ 239 NewAsset: &types.NewAsset{ 240 Changes: &types.AssetDetails{ 241 Decimals: test.RandomPositiveU64(), 242 }, 243 }, 244 }, 245 }, 246 }) 247 248 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.decimals")) 249 } 250 251 func testNewAssetChangeSubmissionWithoutBuiltInAssetFails(t *testing.T) { 252 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 253 Terms: &types.ProposalTerms{ 254 Change: &types.ProposalTerms_NewAsset{ 255 NewAsset: &types.NewAsset{ 256 Changes: &types.AssetDetails{ 257 Source: &types.AssetDetails_BuiltinAsset{}, 258 }, 259 }, 260 }, 261 }, 262 }) 263 264 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.builtin_asset"), commands.ErrIsRequired) 265 } 266 267 func testNewBuiltInAssetChangeSubmissionWithoutMaxFaucetAmountMintFails(t *testing.T) { 268 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 269 Terms: &types.ProposalTerms{ 270 Change: &types.ProposalTerms_NewAsset{ 271 NewAsset: &types.NewAsset{ 272 Changes: &types.AssetDetails{ 273 Source: &types.AssetDetails_BuiltinAsset{ 274 BuiltinAsset: &types.BuiltinAsset{ 275 MaxFaucetAmountMint: "", 276 }, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }) 283 284 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.builtin_asset.max_faucet_amount_mint"), commands.ErrIsRequired) 285 } 286 287 func testNewBuiltInAssetChangeSubmissionWithMaxFaucetAmountMintSucceeds(t *testing.T) { 288 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 289 Terms: &types.ProposalTerms{ 290 Change: &types.ProposalTerms_NewAsset{ 291 NewAsset: &types.NewAsset{ 292 Changes: &types.AssetDetails{ 293 Source: &types.AssetDetails_BuiltinAsset{ 294 BuiltinAsset: &types.BuiltinAsset{ 295 MaxFaucetAmountMint: "10000", 296 }, 297 }, 298 }, 299 }, 300 }, 301 }, 302 }) 303 304 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.builtin_asset.max_faucet_amount_mint")) 305 } 306 307 func testNewBuiltInAssetChangeSubmissionWithNaNMaxFaucetAmountMintFails(t *testing.T) { 308 testCases := []struct { 309 msg string 310 value string 311 error error 312 }{ 313 { 314 msg: "with not-a-number value", 315 value: "hello", 316 error: commands.ErrIsNotValidNumber, 317 }, { 318 msg: "with value of 0", 319 value: "0", 320 error: commands.ErrMustBePositive, 321 }, 322 } 323 for _, tc := range testCases { 324 t.Run(tc.msg, func(t *testing.T) { 325 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 326 Terms: &types.ProposalTerms{ 327 Change: &types.ProposalTerms_NewAsset{ 328 NewAsset: &types.NewAsset{ 329 Changes: &types.AssetDetails{ 330 Source: &types.AssetDetails_BuiltinAsset{ 331 BuiltinAsset: &types.BuiltinAsset{ 332 MaxFaucetAmountMint: tc.value, 333 }, 334 }, 335 }, 336 }, 337 }, 338 }, 339 }) 340 341 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.builtin_asset.max_faucet_amount_mint"), tc.error) 342 }) 343 } 344 } 345 346 func testNewERC20AssetChangeSubmissionWithoutErc20AssetFails(t *testing.T) { 347 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 348 Terms: &types.ProposalTerms{ 349 Change: &types.ProposalTerms_NewAsset{ 350 NewAsset: &types.NewAsset{ 351 Changes: &types.AssetDetails{ 352 Source: &types.AssetDetails_Erc20{}, 353 }, 354 }, 355 }, 356 }, 357 }) 358 359 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20"), commands.ErrIsRequired) 360 } 361 362 func testNewERC20AssetChangeSubmissionWithoutChainIDFails(t *testing.T) { 363 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 364 Terms: &types.ProposalTerms{ 365 Change: &types.ProposalTerms_NewAsset{ 366 NewAsset: &types.NewAsset{ 367 Changes: &types.AssetDetails{ 368 Source: &types.AssetDetails_Erc20{ 369 Erc20: &types.ERC20{ 370 ChainId: "", 371 }, 372 }, 373 }, 374 }, 375 }, 376 }, 377 }) 378 379 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.chain_id"), commands.ErrIsRequired) 380 } 381 382 func testNewERC20AssetChangeSubmissionWithoutContractAddressFails(t *testing.T) { 383 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 384 Terms: &types.ProposalTerms{ 385 Change: &types.ProposalTerms_NewAsset{ 386 NewAsset: &types.NewAsset{ 387 Changes: &types.AssetDetails{ 388 Source: &types.AssetDetails_Erc20{ 389 Erc20: &types.ERC20{ 390 ContractAddress: "", 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 }) 398 399 assert.Contains(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.contract_address"), commands.ErrIsRequired) 400 } 401 402 func testNewERC20AssetChangeSubmissionWithContractAddressSucceeds(t *testing.T) { 403 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 404 Terms: &types.ProposalTerms{ 405 Change: &types.ProposalTerms_NewAsset{ 406 NewAsset: &types.NewAsset{ 407 Changes: &types.AssetDetails{ 408 Source: &types.AssetDetails_Erc20{ 409 Erc20: &types.ERC20{ 410 ContractAddress: "My address", 411 }, 412 }, 413 }, 414 }, 415 }, 416 }, 417 }) 418 419 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.contract_address")) 420 } 421 422 func testNewERC20AssetChangeSubmissionWithInvalidLifetimeLimitFails(t *testing.T) { 423 tcs := []struct { 424 name string 425 err error 426 value string 427 }{ 428 { 429 name: "Without lifetime limit", 430 value: "", 431 err: commands.ErrIsRequired, 432 }, { 433 name: "With not-a-number lifetime limit", 434 value: "forty-two", 435 err: commands.ErrIsNotValidNumber, 436 }, { 437 name: "With zero lifetime limit", 438 value: "0", 439 err: commands.ErrMustBePositive, 440 }, { 441 name: "With negative lifetime limit", 442 value: "-10", 443 err: commands.ErrMustBePositive, 444 }, 445 } 446 447 for _, tc := range tcs { 448 t.Run(tc.name, func(tt *testing.T) { 449 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 450 Terms: &types.ProposalTerms{ 451 Change: &types.ProposalTerms_NewAsset{ 452 NewAsset: &types.NewAsset{ 453 Changes: &types.AssetDetails{ 454 Source: &types.AssetDetails_Erc20{ 455 Erc20: &types.ERC20{ 456 LifetimeLimit: tc.value, 457 }, 458 }, 459 }, 460 }, 461 }, 462 }, 463 }) 464 465 assert.Contains(tt, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.lifetime_limit"), tc.err) 466 }) 467 } 468 } 469 470 func testNewERC20AssetChangeSubmissionWithValidLifetimeLimitSucceeds(t *testing.T) { 471 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 472 Terms: &types.ProposalTerms{ 473 Change: &types.ProposalTerms_NewAsset{ 474 NewAsset: &types.NewAsset{ 475 Changes: &types.AssetDetails{ 476 Source: &types.AssetDetails_Erc20{ 477 Erc20: &types.ERC20{ 478 LifetimeLimit: "100", 479 }, 480 }, 481 }, 482 }, 483 }, 484 }, 485 }) 486 487 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.lifetime_limit")) 488 } 489 490 func testNewERC20AssetChangeSubmissionWithInvalidWithdrawalThresholdFails(t *testing.T) { 491 tcs := []struct { 492 name string 493 err error 494 value string 495 }{ 496 { 497 name: "Without withdraw threshold", 498 value: "", 499 err: commands.ErrIsRequired, 500 }, { 501 name: "With not-a-number withdraw threshold", 502 value: "forty-two", 503 err: commands.ErrIsNotValidNumber, 504 }, { 505 name: "With zero withdraw threshold", 506 value: "0", 507 err: commands.ErrMustBePositive, 508 }, { 509 name: "With negative withdraw threshold", 510 value: "-10", 511 err: commands.ErrMustBePositive, 512 }, 513 } 514 515 for _, tc := range tcs { 516 t.Run(tc.name, func(tt *testing.T) { 517 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 518 Terms: &types.ProposalTerms{ 519 Change: &types.ProposalTerms_NewAsset{ 520 NewAsset: &types.NewAsset{ 521 Changes: &types.AssetDetails{ 522 Source: &types.AssetDetails_Erc20{ 523 Erc20: &types.ERC20{ 524 WithdrawThreshold: tc.value, 525 }, 526 }, 527 }, 528 }, 529 }, 530 }, 531 }) 532 533 assert.Contains(tt, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.withdraw_threshold"), tc.err) 534 }) 535 } 536 } 537 538 func testNewERC20AssetChangeSubmissionWithValidWithdrawalThresholdSucceeds(t *testing.T) { 539 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 540 Terms: &types.ProposalTerms{ 541 Change: &types.ProposalTerms_NewAsset{ 542 NewAsset: &types.NewAsset{ 543 Changes: &types.AssetDetails{ 544 Source: &types.AssetDetails_Erc20{ 545 Erc20: &types.ERC20{ 546 WithdrawThreshold: "100", 547 }, 548 }, 549 }, 550 }, 551 }, 552 }, 553 }) 554 555 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.source.erc20.withdraw_threshold")) 556 } 557 558 func testNewERC20AssetChangeSubmissionWithInvalidQuantumFails(t *testing.T) { 559 tcs := []struct { 560 name string 561 err error 562 value string 563 }{ 564 { 565 name: "Without withdraw quantum", 566 value: "", 567 err: commands.ErrIsRequired, 568 }, { 569 name: "With not-a-number quantum", 570 value: "forty-two", 571 err: commands.ErrIsNotValidNumber, 572 }, { 573 name: "With zero withdraw quantum", 574 value: "0", 575 err: commands.ErrMustBePositive, 576 }, { 577 name: "With negative withdraw quantum", 578 value: "-10", 579 err: commands.ErrMustBePositive, 580 }, 581 } 582 583 for _, tc := range tcs { 584 t.Run(tc.name, func(tt *testing.T) { 585 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 586 Terms: &types.ProposalTerms{ 587 Change: &types.ProposalTerms_NewAsset{ 588 NewAsset: &types.NewAsset{ 589 Changes: &types.AssetDetails{ 590 Quantum: tc.value, 591 Source: &types.AssetDetails_Erc20{ 592 Erc20: &types.ERC20{}, 593 }, 594 }, 595 }, 596 }, 597 }, 598 }) 599 600 assert.Contains(tt, err.Get("proposal_submission.terms.change.new_asset.changes.quantum"), tc.err) 601 }) 602 } 603 } 604 605 func testNewERC20AssetChangeSubmissionWithValidQuantumSucceeds(t *testing.T) { 606 err := checkProposalSubmission(&commandspb.ProposalSubmission{ 607 Terms: &types.ProposalTerms{ 608 Change: &types.ProposalTerms_NewAsset{ 609 NewAsset: &types.NewAsset{ 610 Changes: &types.AssetDetails{ 611 Quantum: "0.1", 612 Source: &types.AssetDetails_Erc20{ 613 Erc20: &types.ERC20{}, 614 }, 615 }, 616 }, 617 }, 618 }, 619 }) 620 621 assert.Empty(t, err.Get("proposal_submission.terms.change.new_asset.changes.quantum")) 622 }