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  }